Spaces:
Running
Running
eeshaAI commited on
Commit ·
94c9882
1
Parent(s): f8e9acc
feat: Migrate frontend from static HTML to Next.js 16 App Router
Browse files- All 16 buyer pages: home, login, product, cart, checkout, payment,
categories, search, seller-profile, profile, orders, wishlist,
videos, live, live-watch, group-buy, ai-chat, link-account, telegram
- 7 seller dashboard pages with shared sidebar layout
- 22 API route proxies to existing Supabase Edge Functions
- Suspense boundaries for useSearchParams pages
- Standalone output build for Docker deployment
- Dockerfile (multi-stage Node 20 alpine, port 7860)
- README with HF Space YAML frontmatter
- .hfignore for lean Docker context
Backend unchanged: 27 Supabase Edge Functions, Render bot, OpenWA,
PalmPay payment verifier — all remain in place.
This view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +54 -9
- .hfignore +49 -0
- Dockerfile +28 -17
- Eesha buying folder/auth.html +0 -246
- Eesha buying folder/cart.html +0 -651
- Eesha buying folder/categories.html +0 -689
- Eesha buying folder/checkout.html +0 -858
- Eesha buying folder/css/index.css +0 -72
- Eesha buying folder/css/loader.css +0 -0
- Eesha buying folder/css/product.css +0 -0
- Eesha buying folder/css/style.css +0 -203
- Eesha buying folder/js/cart.js +0 -0
- Eesha buying folder/js/loader.js +0 -0
- Eesha buying folder/js/product.js +0 -536
- Eesha buying folder/login.html +0 -153
- Eesha buying folder/order-confirmation.html +0 -450
- Eesha buying folder/orders.html +0 -422
- Eesha buying folder/product.html +0 -355
- Eesha buying folder/profile.html +0 -356
- Eesha buying folder/settings.html +0 -524
- Eesha buying folder/signup.html +0 -135
- Eesha buying folder/wishlist.html +0 -315
- README.md +43 -19
- ai-chat.html +0 -235
- auth.html +0 -246
- bun.lock +0 -0
- cart.html +0 -653
- categories.html +0 -611
- checkout.html +0 -891
- components.json +21 -0
- css/common.css +0 -360
- css/index.css +0 -747
- css/loader.css +0 -0
- css/product.css +0 -0
- css/style.css +0 -281
- css/styles.css +0 -0
- dist/style.css +0 -1
- eeshapay-logo.html +0 -54
- eslint.config.mjs +50 -0
- group-buy.html +0 -200
- index.html +0 -1448
- js/ai-assistant.js +0 -301
- js/ai-buyer-assistant.js +0 -962
- js/cart.js +0 -203
- js/config/edge-functions.js +0 -354
- js/load-navbar.js +0 -23
- js/loader.js +0 -0
- js/main.js +0 -117
- js/product.js +0 -536
- link-account.html +0 -165
.gitignore
CHANGED
|
@@ -1,9 +1,54 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
.
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# dependencies
|
| 4 |
+
node_modules
|
| 5 |
+
/.pnp
|
| 6 |
+
.pnp.*
|
| 7 |
+
.yarn/*
|
| 8 |
+
!.yarn/patches
|
| 9 |
+
!.yarn/plugins
|
| 10 |
+
!.yarn/releases
|
| 11 |
+
!.yarn/versions
|
| 12 |
+
|
| 13 |
+
# testing
|
| 14 |
+
/coverage
|
| 15 |
+
|
| 16 |
+
# next.js
|
| 17 |
+
/.next/
|
| 18 |
+
/out/
|
| 19 |
+
|
| 20 |
+
# production
|
| 21 |
+
/build
|
| 22 |
+
|
| 23 |
+
# misc
|
| 24 |
+
.DS_Store
|
| 25 |
+
*.pem
|
| 26 |
+
|
| 27 |
+
# debug
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-debug.log*
|
| 30 |
+
yarn-error.log*
|
| 31 |
+
.pnpm-debug.log*
|
| 32 |
+
|
| 33 |
+
# env files (can opt-in for committing if needed)
|
| 34 |
+
.env*
|
| 35 |
+
|
| 36 |
+
# vercel
|
| 37 |
+
.vercel
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# typescript
|
| 41 |
+
*.tsbuildinfo
|
| 42 |
+
next-env.d.ts
|
| 43 |
+
local-*
|
| 44 |
+
.claude
|
| 45 |
+
.z-ai-config
|
| 46 |
+
*.log
|
| 47 |
+
dev.log
|
| 48 |
+
dev.out.log
|
| 49 |
+
test
|
| 50 |
+
prompt
|
| 51 |
+
|
| 52 |
+
server.log
|
| 53 |
+
# Skills directory
|
| 54 |
+
/skills/
|
.hfignore
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces deployment ignore file
|
| 2 |
+
# Keep the Docker image build lean
|
| 3 |
+
|
| 4 |
+
# Dependencies (rebuilt in Docker)
|
| 5 |
+
node_modules
|
| 6 |
+
.pnp
|
| 7 |
+
.pnp.*
|
| 8 |
+
.yarn/*
|
| 9 |
+
|
| 10 |
+
# Build artifacts (rebuilt in Docker)
|
| 11 |
+
.next
|
| 12 |
+
out
|
| 13 |
+
dist
|
| 14 |
+
build
|
| 15 |
+
|
| 16 |
+
# Python (this is a Node-only deployment)
|
| 17 |
+
scripts
|
| 18 |
+
skills
|
| 19 |
+
examples
|
| 20 |
+
mini-services
|
| 21 |
+
|
| 22 |
+
# Prisma local DB
|
| 23 |
+
db
|
| 24 |
+
prisma
|
| 25 |
+
|
| 26 |
+
# Logs & dev artifacts
|
| 27 |
+
*.log
|
| 28 |
+
dev.log
|
| 29 |
+
server.log
|
| 30 |
+
worklog.md
|
| 31 |
+
agents.md
|
| 32 |
+
|
| 33 |
+
# Caddy / reverse proxy (not needed inside HF Space)
|
| 34 |
+
Caddyfile
|
| 35 |
+
|
| 36 |
+
# Original upload / download folders (not part of web app)
|
| 37 |
+
upload
|
| 38 |
+
download
|
| 39 |
+
tool-results
|
| 40 |
+
|
| 41 |
+
# Git
|
| 42 |
+
.git
|
| 43 |
+
.gitignore
|
| 44 |
+
.github
|
| 45 |
+
|
| 46 |
+
# Local env files (HF Spaces has its own secrets UI)
|
| 47 |
+
.env
|
| 48 |
+
.env.local
|
| 49 |
+
.env.*.local
|
Dockerfile
CHANGED
|
@@ -1,27 +1,38 @@
|
|
| 1 |
-
|
|
|
|
| 2 |
|
| 3 |
-
#
|
| 4 |
-
|
| 5 |
-
build-essential \
|
| 6 |
-
curl \
|
| 7 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 8 |
|
| 9 |
WORKDIR /app
|
| 10 |
|
| 11 |
-
#
|
| 12 |
-
COPY
|
| 13 |
-
RUN
|
| 14 |
|
| 15 |
-
# Copy
|
| 16 |
COPY . .
|
| 17 |
|
| 18 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
ENV PORT=7860
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
#
|
| 23 |
-
|
| 24 |
-
CMD curl -f http://localhost:7860/api/health || exit 1
|
| 25 |
|
| 26 |
-
# Run the
|
| 27 |
-
CMD ["
|
|
|
|
| 1 |
+
# Cellex — Hugging Face Space Deployment
|
| 2 |
+
# Docker space that builds the Next.js standalone output and serves it on port 7860
|
| 3 |
|
| 4 |
+
# ---- Stage 1: Build ----
|
| 5 |
+
FROM node:20-alpine AS builder
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
WORKDIR /app
|
| 8 |
|
| 9 |
+
# Install dependencies (use npm ci for reproducible builds)
|
| 10 |
+
COPY package*.json ./
|
| 11 |
+
RUN npm ci --no-audit --no-fund
|
| 12 |
|
| 13 |
+
# Copy source
|
| 14 |
COPY . .
|
| 15 |
|
| 16 |
+
# Build the Next.js standalone output
|
| 17 |
+
RUN npm run build
|
| 18 |
+
|
| 19 |
+
# ---- Stage 2: Run ----
|
| 20 |
+
FROM node:20-alpine AS runner
|
| 21 |
+
|
| 22 |
+
WORKDIR /app
|
| 23 |
+
|
| 24 |
+
# Set production env
|
| 25 |
+
ENV NODE_ENV=production
|
| 26 |
ENV PORT=7860
|
| 27 |
+
ENV HOSTNAME=0.0.0.0
|
| 28 |
+
|
| 29 |
+
# Copy standalone build + public assets + static
|
| 30 |
+
COPY --from=builder /app/.next/standalone ./
|
| 31 |
+
COPY --from=builder /app/.next/static ./.next/static
|
| 32 |
+
COPY --from=builder /app/public ./public
|
| 33 |
|
| 34 |
+
# HF Spaces runs on port 7860 by default
|
| 35 |
+
EXPOSE 7860
|
|
|
|
| 36 |
|
| 37 |
+
# Run the standalone Next.js server
|
| 38 |
+
CMD ["node", "server.js"]
|
Eesha buying folder/auth.html
DELETED
|
@@ -1,246 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Login / Signup - Eesha</title>
|
| 7 |
-
<link rel="stylesheet" href="css/style.css">
|
| 8 |
-
|
| 9 |
-
</head>
|
| 10 |
-
<body class="auth-body">
|
| 11 |
-
|
| 12 |
-
<div class="auth-container">
|
| 13 |
-
<div id="login-form">
|
| 14 |
-
<h2>Login to Eesha</h2>
|
| 15 |
-
<form id="login">
|
| 16 |
-
<input type="email" id="login-email" placeholder="Email" required>
|
| 17 |
-
<input type="password" id="login-password" placeholder="Password" required>
|
| 18 |
-
<button type="submit">Login</button>
|
| 19 |
-
</form>
|
| 20 |
-
<p>Don't have an account? <a href="#" id="show-signup">Sign up</a></p>
|
| 21 |
-
</div>
|
| 22 |
-
|
| 23 |
-
<div id="signup-form" style="display:none;">
|
| 24 |
-
<h2>Create an Account</h2>
|
| 25 |
-
<form id="signup">
|
| 26 |
-
<input type="text" id="signup-name" placeholder="Full Name" required>
|
| 27 |
-
<input type="email" id="signup-email" placeholder="Email" required>
|
| 28 |
-
<input type="password" id="signup-password" placeholder="Password" required>
|
| 29 |
-
<div class="user-type">
|
| 30 |
-
<label>I am a:</label>
|
| 31 |
-
<input type="radio" id="buyer" name="user_type" value="buyer" checked>
|
| 32 |
-
<label for="buyer">Buyer</label>
|
| 33 |
-
<input type="radio" id="seller" name="user_type" value="seller">
|
| 34 |
-
<label for="seller">Seller</label>
|
| 35 |
-
</div>
|
| 36 |
-
<button type="submit">Sign Up</button>
|
| 37 |
-
</form>
|
| 38 |
-
<p>Already have an account? <a href="#" id="show-login">Log in</a></p>
|
| 39 |
-
</div>
|
| 40 |
-
<a href="../eesha selling folder/login.html" style="text-decoration: none;"> Am a Seller</a>
|
| 41 |
-
</div>
|
| 42 |
-
<script>/* General Body & Layout */
|
| 43 |
-
body {
|
| 44 |
-
font-family: Arial, sans-serif;
|
| 45 |
-
margin: 0;
|
| 46 |
-
background-color: #f4f4f4;
|
| 47 |
-
color: #333;
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
.container {
|
| 51 |
-
width: 90%;
|
| 52 |
-
margin: 0 auto;
|
| 53 |
-
max-width: 1200px;
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
/* Header */
|
| 57 |
-
header {
|
| 58 |
-
background-color: #2c3e50;
|
| 59 |
-
color: white;
|
| 60 |
-
padding: 1rem 0;
|
| 61 |
-
display: flex;
|
| 62 |
-
justify-content: space-between;
|
| 63 |
-
align-items: center;
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
header h1 {
|
| 67 |
-
margin: 0;
|
| 68 |
-
font-size: 1.8rem;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
header nav a, header nav button {
|
| 72 |
-
color: white;
|
| 73 |
-
text-decoration: none;
|
| 74 |
-
background: #3498db;
|
| 75 |
-
padding: 0.5rem 1rem;
|
| 76 |
-
border-radius: 5px;
|
| 77 |
-
border: none;
|
| 78 |
-
cursor: pointer;
|
| 79 |
-
}
|
| 80 |
-
header nav span {
|
| 81 |
-
margin-right: 15px;
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
/* Product Grid */
|
| 85 |
-
.product-grid {
|
| 86 |
-
display: grid;
|
| 87 |
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
| 88 |
-
gap: 1.5rem;
|
| 89 |
-
margin-top: 2rem;
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
.product-card {
|
| 93 |
-
background-color: white;
|
| 94 |
-
border: 1px solid #ddd;
|
| 95 |
-
border-radius: 8px;
|
| 96 |
-
overflow: hidden;
|
| 97 |
-
text-align: center;
|
| 98 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 99 |
-
cursor: pointer;
|
| 100 |
-
transition: transform 0.2s, box-shadow 0.2s;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
.product-card:hover {
|
| 104 |
-
transform: translateY(-5px);
|
| 105 |
-
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
.product-card img {
|
| 109 |
-
width: 100%;
|
| 110 |
-
height: 180px;
|
| 111 |
-
object-fit: cover;
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
.product-card h3 {
|
| 115 |
-
margin: 0.5rem 0;
|
| 116 |
-
font-size: 1.1rem;
|
| 117 |
-
padding: 0 10px;
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
.product-card .price {
|
| 121 |
-
color: #e74c3c;
|
| 122 |
-
font-weight: bold;
|
| 123 |
-
font-size: 1.2rem;
|
| 124 |
-
margin-bottom: 1rem;
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
/* Auth Page */
|
| 128 |
-
.auth-body {
|
| 129 |
-
display: flex;
|
| 130 |
-
justify-content: center;
|
| 131 |
-
align-items: center;
|
| 132 |
-
height: 100vh;
|
| 133 |
-
background: linear-gradient(135deg, #3498db, #2c3e50);
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
.auth-container {
|
| 137 |
-
background-color: white;
|
| 138 |
-
padding: 2rem;
|
| 139 |
-
border-radius: 10px;
|
| 140 |
-
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
| 141 |
-
width: 100%;
|
| 142 |
-
max-width: 400px;
|
| 143 |
-
text-align: center;
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
.auth-container h2 {
|
| 147 |
-
margin-bottom: 1.5rem;
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
.auth-container input[type="text"],
|
| 151 |
-
.auth-container input[type="email"],
|
| 152 |
-
.auth-container input[type="password"] {
|
| 153 |
-
width: calc(100% - 20px);
|
| 154 |
-
padding: 10px;
|
| 155 |
-
margin-bottom: 1rem;
|
| 156 |
-
border: 1px solid #ccc;
|
| 157 |
-
border-radius: 5px;
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
.auth-container button {
|
| 161 |
-
width: 100%;
|
| 162 |
-
padding: 12px;
|
| 163 |
-
background-color: #2ecc71;
|
| 164 |
-
color: white;
|
| 165 |
-
border: none;
|
| 166 |
-
border-radius: 5px;
|
| 167 |
-
cursor: pointer;
|
| 168 |
-
font-size: 1rem;
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
.auth-container p {
|
| 172 |
-
margin-top: 1rem;
|
| 173 |
-
}
|
| 174 |
-
.auth-container a {
|
| 175 |
-
color: #3498db;
|
| 176 |
-
text-decoration: none;
|
| 177 |
-
font-weight: bold;
|
| 178 |
-
}
|
| 179 |
-
.user-type {
|
| 180 |
-
margin-bottom: 1rem;
|
| 181 |
-
text-align: left;
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
/* Dashboard */
|
| 185 |
-
.dashboard-form {
|
| 186 |
-
background: white;
|
| 187 |
-
padding: 1.5rem;
|
| 188 |
-
border-radius: 8px;
|
| 189 |
-
margin-bottom: 2rem;
|
| 190 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 191 |
-
}
|
| 192 |
-
.dashboard-form input, .dashboard-form textarea {
|
| 193 |
-
width: calc(100% - 22px);
|
| 194 |
-
padding: 10px;
|
| 195 |
-
margin-bottom: 10px;
|
| 196 |
-
border: 1px solid #ccc;
|
| 197 |
-
border-radius: 4px;
|
| 198 |
-
}
|
| 199 |
-
.dashboard-form button {
|
| 200 |
-
background-color: #2c3e50;
|
| 201 |
-
color: white;
|
| 202 |
-
padding: 10px 20px;
|
| 203 |
-
border: none;
|
| 204 |
-
border-radius: 4px;
|
| 205 |
-
cursor: pointer;
|
| 206 |
-
}
|
| 207 |
-
|
| 208 |
-
/* AI Assistant */
|
| 209 |
-
.ai-assistant {
|
| 210 |
-
background-color: #eaf5ff;
|
| 211 |
-
border: 1px solid #bde0ff;
|
| 212 |
-
border-radius: 8px;
|
| 213 |
-
padding: 1.5rem;
|
| 214 |
-
margin-bottom: 2rem;
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
.ai-assistant h3 {
|
| 218 |
-
margin-top: 0;
|
| 219 |
-
}
|
| 220 |
-
|
| 221 |
-
.ai-chat-box {
|
| 222 |
-
display: flex;
|
| 223 |
-
gap: 10px;
|
| 224 |
-
}
|
| 225 |
-
|
| 226 |
-
.ai-chat-box input {
|
| 227 |
-
flex-grow: 1;
|
| 228 |
-
padding: 10px;
|
| 229 |
-
border: 1px solid #ccc;
|
| 230 |
-
border-radius: 4px;
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
.ai-chat-box button {
|
| 234 |
-
padding: 10px 20px;
|
| 235 |
-
background-color: #3498db;
|
| 236 |
-
color: white;
|
| 237 |
-
border: none;
|
| 238 |
-
border-radius: 4px;
|
| 239 |
-
cursor: pointer;
|
| 240 |
-
}
|
| 241 |
-
|
| 242 |
-
#ai-results {
|
| 243 |
-
margin-top: 1rem;
|
| 244 |
-
}</script>
|
| 245 |
-
</body>
|
| 246 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/cart.html
DELETED
|
@@ -1,651 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Shopping Cart - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<script src="../js/cart.js"></script>
|
| 9 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
|
| 44 |
-
|
| 45 |
-
* {
|
| 46 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
.loading-spinner {
|
| 50 |
-
animation: spin 1s linear infinite;
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
@keyframes spin {
|
| 54 |
-
0% { transform: rotate(0deg); }
|
| 55 |
-
100% { transform: rotate(360deg); }
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
@keyframes fadeIn {
|
| 59 |
-
from { opacity: 0; transform: translateY(10px); }
|
| 60 |
-
to { opacity: 1; transform: translateY(0); }
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
@keyframes slideIn {
|
| 64 |
-
from { opacity: 0; transform: translateX(-20px); }
|
| 65 |
-
to { opacity: 1; transform: translateX(0); }
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
.fade-in {
|
| 69 |
-
animation: fadeIn 0.3s ease-out forwards;
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
.slide-in {
|
| 73 |
-
animation: slideIn 0.3s ease-out forwards;
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
.cart-item {
|
| 77 |
-
animation: fadeIn 0.3s ease-out forwards;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
.cart-item:nth-child(1) { animation-delay: 0.05s; }
|
| 81 |
-
.cart-item:nth-child(2) { animation-delay: 0.1s; }
|
| 82 |
-
.cart-item:nth-child(3) { animation-delay: 0.15s; }
|
| 83 |
-
.cart-item:nth-child(4) { animation-delay: 0.2s; }
|
| 84 |
-
.cart-item:nth-child(5) { animation-delay: 0.25s; }
|
| 85 |
-
|
| 86 |
-
.quantity-btn {
|
| 87 |
-
transition: all 0.2s ease;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
.quantity-btn:hover {
|
| 91 |
-
transform: scale(1.1);
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
.quantity-btn:active {
|
| 95 |
-
transform: scale(0.95);
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
.product-image {
|
| 99 |
-
transition: transform 0.3s ease;
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
.product-image:hover {
|
| 103 |
-
transform: scale(1.05);
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
.checkout-btn {
|
| 107 |
-
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
| 108 |
-
transition: all 0.3s ease;
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
.checkout-btn:hover {
|
| 112 |
-
transform: translateY(-2px);
|
| 113 |
-
box-shadow: 0 10px 30px rgba(245, 158, 11, 0.3);
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
.checkout-btn:active {
|
| 117 |
-
transform: translateY(0);
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
.summary-card {
|
| 121 |
-
background: linear-gradient(180deg, #ffffff 0%, #fafafa 100%);
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
.delete-btn {
|
| 125 |
-
transition: all 0.2s ease;
|
| 126 |
-
}
|
| 127 |
-
|
| 128 |
-
.delete-btn:hover {
|
| 129 |
-
background: #fee2e2;
|
| 130 |
-
color: #dc2626;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
.promo-input:focus {
|
| 134 |
-
box-shadow: 0 0 0 3px rgba(240, 71, 74, 0.1);
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
.skeleton {
|
| 138 |
-
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
| 139 |
-
background-size: 200% 100%;
|
| 140 |
-
animation: shimmer 1.5s infinite;
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
@keyframes shimmer {
|
| 144 |
-
0% { background-position: -200% 0; }
|
| 145 |
-
100% { background-position: 200% 0; }
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
.empty-cart-icon {
|
| 149 |
-
animation: float 3s ease-in-out infinite;
|
| 150 |
-
}
|
| 151 |
-
|
| 152 |
-
@keyframes float {
|
| 153 |
-
0%, 100% { transform: translateY(0); }
|
| 154 |
-
50% { transform: translateY(-10px); }
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
/* Custom scrollbar */
|
| 158 |
-
::-webkit-scrollbar {
|
| 159 |
-
width: 6px;
|
| 160 |
-
}
|
| 161 |
-
|
| 162 |
-
::-webkit-scrollbar-track {
|
| 163 |
-
background: #f1f1f1;
|
| 164 |
-
border-radius: 10px;
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
::-webkit-scrollbar-thumb {
|
| 168 |
-
background: #c1c1c1;
|
| 169 |
-
border-radius: 10px;
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
::-webkit-scrollbar-thumb:hover {
|
| 173 |
-
background: #a1a1a1;
|
| 174 |
-
}
|
| 175 |
-
</style>
|
| 176 |
-
</head>
|
| 177 |
-
<body class="bg-gray-50 min-h-screen">
|
| 178 |
-
<!-- Header -->
|
| 179 |
-
<header class="bg-white shadow-sm sticky top-0 z-50">
|
| 180 |
-
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 181 |
-
<div class="flex justify-between items-center h-16">
|
| 182 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 183 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 184 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 185 |
-
<defs>
|
| 186 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 187 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 188 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 189 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 190 |
-
</linearGradient>
|
| 191 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 192 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 193 |
-
<feMerge>
|
| 194 |
-
<feMergeNode in="coloredBlur"/>
|
| 195 |
-
<feMergeNode in="SourceGraphic"/>
|
| 196 |
-
</feMerge>
|
| 197 |
-
</filter>
|
| 198 |
-
</defs>
|
| 199 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 200 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 201 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 202 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 203 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 204 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 205 |
-
<g transform="translate(52, 8)">
|
| 206 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 207 |
-
</g>
|
| 208 |
-
</svg>
|
| 209 |
-
</div>
|
| 210 |
-
<div class="flex flex-col">
|
| 211 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 212 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 213 |
-
</div>
|
| 214 |
-
</a>
|
| 215 |
-
<div class="flex items-center space-x-4">
|
| 216 |
-
<a href="../index.html" class="text-gray-600 hover:text-brand-500 transition-colors">
|
| 217 |
-
<i class="fas fa-home text-xl"></i>
|
| 218 |
-
</a>
|
| 219 |
-
<a href="cart.html" class="relative text-brand-500">
|
| 220 |
-
<i class="fas fa-shopping-cart text-xl"></i>
|
| 221 |
-
<span id="cartCount" class="absolute -top-2 -right-2 bg-brand-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 222 |
-
</a>
|
| 223 |
-
</div>
|
| 224 |
-
</div>
|
| 225 |
-
</nav>
|
| 226 |
-
</header>
|
| 227 |
-
|
| 228 |
-
<!-- Breadcrumb -->
|
| 229 |
-
<div class="bg-white border-b">
|
| 230 |
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
| 231 |
-
<nav class="flex items-center space-x-2 text-sm text-gray-500">
|
| 232 |
-
<a href="../index.html" class="hover:text-brand-500 transition-colors">Home</a>
|
| 233 |
-
<i class="fas fa-chevron-right text-xs"></i>
|
| 234 |
-
<span class="text-gray-900 font-medium">Shopping Cart</span>
|
| 235 |
-
</nav>
|
| 236 |
-
</div>
|
| 237 |
-
</div>
|
| 238 |
-
|
| 239 |
-
<!-- Main Content -->
|
| 240 |
-
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
| 241 |
-
<!-- Page Title -->
|
| 242 |
-
<div class="flex items-center justify-between mb-8">
|
| 243 |
-
<div>
|
| 244 |
-
<h1 class="text-3xl font-bold text-gray-900">Shopping Cart</h1>
|
| 245 |
-
<p class="text-gray-500 mt-1" id="cartSummary">Loading your cart...</p>
|
| 246 |
-
</div>
|
| 247 |
-
<a href="../index.html" class="hidden sm:flex items-center text-brand-500 hover:text-brand-600 transition-colors font-medium">
|
| 248 |
-
<i class="fas fa-arrow-left mr-2"></i>
|
| 249 |
-
Continue Shopping
|
| 250 |
-
</a>
|
| 251 |
-
</div>
|
| 252 |
-
|
| 253 |
-
<!-- Cart Content -->
|
| 254 |
-
<div id="cartContent">
|
| 255 |
-
<!-- Loading Skeleton -->
|
| 256 |
-
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 257 |
-
<div class="lg:col-span-2 space-y-4">
|
| 258 |
-
<div class="bg-white rounded-2xl p-6 shadow-sm">
|
| 259 |
-
<div class="flex items-center space-x-4">
|
| 260 |
-
<div class="skeleton w-24 h-24 rounded-xl"></div>
|
| 261 |
-
<div class="flex-1 space-y-3">
|
| 262 |
-
<div class="skeleton h-4 w-3/4 rounded"></div>
|
| 263 |
-
<div class="skeleton h-3 w-1/2 rounded"></div>
|
| 264 |
-
<div class="skeleton h-6 w-1/4 rounded"></div>
|
| 265 |
-
</div>
|
| 266 |
-
</div>
|
| 267 |
-
</div>
|
| 268 |
-
<div class="bg-white rounded-2xl p-6 shadow-sm">
|
| 269 |
-
<div class="flex items-center space-x-4">
|
| 270 |
-
<div class="skeleton w-24 h-24 rounded-xl"></div>
|
| 271 |
-
<div class="flex-1 space-y-3">
|
| 272 |
-
<div class="skeleton h-4 w-3/4 rounded"></div>
|
| 273 |
-
<div class="skeleton h-3 w-1/2 rounded"></div>
|
| 274 |
-
<div class="skeleton h-6 w-1/4 rounded"></div>
|
| 275 |
-
</div>
|
| 276 |
-
</div>
|
| 277 |
-
</div>
|
| 278 |
-
</div>
|
| 279 |
-
<div class="lg:col-span-1">
|
| 280 |
-
<div class="bg-white rounded-2xl p-6 shadow-sm">
|
| 281 |
-
<div class="skeleton h-6 w-1/2 rounded mb-6"></div>
|
| 282 |
-
<div class="space-y-4">
|
| 283 |
-
<div class="skeleton h-4 w-full rounded"></div>
|
| 284 |
-
<div class="skeleton h-4 w-3/4 rounded"></div>
|
| 285 |
-
<div class="skeleton h-10 w-full rounded mt-6"></div>
|
| 286 |
-
</div>
|
| 287 |
-
</div>
|
| 288 |
-
</div>
|
| 289 |
-
</div>
|
| 290 |
-
</div>
|
| 291 |
-
</main>
|
| 292 |
-
|
| 293 |
-
<!-- Footer -->
|
| 294 |
-
<footer class="bg-dark-900 text-white py-8 mt-12">
|
| 295 |
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
| 296 |
-
<p>© 2025 Cellex. All rights reserved.</p>
|
| 297 |
-
</div>
|
| 298 |
-
</footer>
|
| 299 |
-
|
| 300 |
-
<script>
|
| 301 |
-
let currentUser = null;
|
| 302 |
-
|
| 303 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 304 |
-
await checkAuth();
|
| 305 |
-
await loadCart();
|
| 306 |
-
});
|
| 307 |
-
|
| 308 |
-
async function checkAuth() {
|
| 309 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 310 |
-
currentUser = user || null;
|
| 311 |
-
}
|
| 312 |
-
|
| 313 |
-
async function loadCart() {
|
| 314 |
-
const container = document.getElementById('cartContent');
|
| 315 |
-
|
| 316 |
-
// If not logged in, show login prompt
|
| 317 |
-
if (!currentUser) {
|
| 318 |
-
renderLoginPrompt();
|
| 319 |
-
return;
|
| 320 |
-
}
|
| 321 |
-
|
| 322 |
-
try {
|
| 323 |
-
const cartItems = await window.Cart.getCartItems();
|
| 324 |
-
|
| 325 |
-
if (!cartItems || cartItems.length === 0) {
|
| 326 |
-
renderEmptyCart();
|
| 327 |
-
return;
|
| 328 |
-
}
|
| 329 |
-
|
| 330 |
-
const cartTotal = cartItems.reduce((sum, item) => {
|
| 331 |
-
return sum + ((item.products?.price || 0) * (item.quantity || 1));
|
| 332 |
-
}, 0);
|
| 333 |
-
|
| 334 |
-
// Calculate total quantity for summary
|
| 335 |
-
const totalItems = cartItems.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
| 336 |
-
document.getElementById('cartSummary').textContent = `${totalItems} item${totalItems !== 1 ? 's' : ''} in your cart`;
|
| 337 |
-
|
| 338 |
-
container.innerHTML = `
|
| 339 |
-
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 340 |
-
<!-- Cart Items -->
|
| 341 |
-
<div class="lg:col-span-2 space-y-4">
|
| 342 |
-
${cartItems.map((item, index) => {
|
| 343 |
-
if (!item.products) return '';
|
| 344 |
-
return renderCartItem(item.products, item.quantity, index, item.id);
|
| 345 |
-
}).join('')}
|
| 346 |
-
|
| 347 |
-
<!-- Continue Shopping -->
|
| 348 |
-
<a href="../index.html" class="flex items-center justify-center py-4 border-2 border-dashed border-gray-200 rounded-xl text-gray-500 hover:text-brand-500 hover:border-brand-400 transition-colors">
|
| 349 |
-
<i class="fas fa-plus mr-2"></i>
|
| 350 |
-
Add more items
|
| 351 |
-
</a>
|
| 352 |
-
</div>
|
| 353 |
-
|
| 354 |
-
<!-- Order Summary -->
|
| 355 |
-
${renderOrderSummary(cartTotal, true)}
|
| 356 |
-
</div>
|
| 357 |
-
`;
|
| 358 |
-
|
| 359 |
-
// Update cart count in header
|
| 360 |
-
if (window.Cart) {
|
| 361 |
-
await window.Cart.updateCartCountUI();
|
| 362 |
-
}
|
| 363 |
-
} catch (error) {
|
| 364 |
-
console.error('Error loading cart:', error);
|
| 365 |
-
container.innerHTML = `
|
| 366 |
-
<div class="bg-white rounded-2xl p-12 text-center shadow-sm">
|
| 367 |
-
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
| 368 |
-
<i class="fas fa-exclamation-triangle text-2xl text-red-500"></i>
|
| 369 |
-
</div>
|
| 370 |
-
<h3 class="text-xl font-semibold text-gray-900 mb-2">Error Loading Cart</h3>
|
| 371 |
-
<p class="text-gray-500 mb-6">There was a problem loading your cart. Please try again.</p>
|
| 372 |
-
<button onclick="loadCart()" class="text-brand-500 font-medium hover:underline">
|
| 373 |
-
<i class="fas fa-redo mr-2"></i>Try Again
|
| 374 |
-
</button>
|
| 375 |
-
</div>
|
| 376 |
-
`;
|
| 377 |
-
}
|
| 378 |
-
}
|
| 379 |
-
|
| 380 |
-
function renderLoginPrompt() {
|
| 381 |
-
document.getElementById('cartSummary').textContent = 'Please login to view your cart';
|
| 382 |
-
document.getElementById('cartContent').innerHTML = `
|
| 383 |
-
<div class="bg-white rounded-2xl p-12 text-center shadow-sm max-w-2xl mx-auto fade-in">
|
| 384 |
-
<div class="w-32 h-32 bg-brand-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
| 385 |
-
<i class="fas fa-user-lock text-5xl text-brand-400"></i>
|
| 386 |
-
</div>
|
| 387 |
-
<h2 class="text-2xl font-bold text-gray-900 mb-2">Login Required</h2>
|
| 388 |
-
<p class="text-gray-500 mb-8">Please login to view and manage your shopping cart. Your cart will be saved across all your devices.</p>
|
| 389 |
-
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
| 390 |
-
<a href="login.html?redirect=cart" class="px-8 py-3 bg-brand-500 text-white font-semibold rounded-xl hover:bg-brand-600 transition-colors">
|
| 391 |
-
<i class="fas fa-sign-in-alt mr-2"></i>Login
|
| 392 |
-
</a>
|
| 393 |
-
<a href="signup.html?redirect=cart" class="px-8 py-3 border-2 border-brand-500 text-brand-500 font-semibold rounded-xl hover:bg-brand-50 transition-colors">
|
| 394 |
-
<i class="fas fa-user-plus mr-2"></i>Create Account
|
| 395 |
-
</a>
|
| 396 |
-
</div>
|
| 397 |
-
</div>
|
| 398 |
-
`;
|
| 399 |
-
}
|
| 400 |
-
|
| 401 |
-
function renderCartItem(product, quantity, index, cartItemId = null) {
|
| 402 |
-
const discount = product.original_price ? Math.round(((product.original_price - product.price) / product.original_price) * 100) : 0;
|
| 403 |
-
const image = product.image || product.image_url || 'https://via.placeholder.com/200';
|
| 404 |
-
const productId = product.id;
|
| 405 |
-
|
| 406 |
-
return `
|
| 407 |
-
<div class="cart-item bg-white rounded-2xl p-4 sm:p-6 shadow-sm hover:shadow-md transition-shadow" data-product-id="${productId}" data-cart-id="${cartItemId || ''}">
|
| 408 |
-
<div class="flex items-start gap-4">
|
| 409 |
-
<!-- Product Image -->
|
| 410 |
-
<div class="flex-shrink-0">
|
| 411 |
-
<a href="product.html?id=${productId}">
|
| 412 |
-
<img src="${image}" alt="${product.name || 'Product'}"
|
| 413 |
-
class="product-image w-24 h-24 sm:w-32 sm:h-32 rounded-xl object-cover cursor-pointer">
|
| 414 |
-
</a>
|
| 415 |
-
</div>
|
| 416 |
-
|
| 417 |
-
<!-- Product Details -->
|
| 418 |
-
<div class="flex-1 min-w-0">
|
| 419 |
-
<div class="flex items-start justify-between gap-2">
|
| 420 |
-
<div>
|
| 421 |
-
<a href="product.html?id=${productId}" class="text-gray-400 text-xs uppercase tracking-wide">${product.category || 'Category'}</a>
|
| 422 |
-
<h3 class="text-lg font-semibold text-gray-900 mt-1 hover:text-brand-500 transition-colors">
|
| 423 |
-
<a href="product.html?id=${productId}">${product.name || 'Product Name'}</a>
|
| 424 |
-
</h3>
|
| 425 |
-
</div>
|
| 426 |
-
<button onclick="removeItem('${productId}', '${cartItemId}')"
|
| 427 |
-
class="delete-btn p-2 rounded-lg text-gray-400 hover:text-red-500">
|
| 428 |
-
<i class="fas fa-trash-alt"></i>
|
| 429 |
-
</button>
|
| 430 |
-
</div>
|
| 431 |
-
|
| 432 |
-
<!-- Price & Discount -->
|
| 433 |
-
<div class="flex items-center gap-3 mt-2">
|
| 434 |
-
<span class="text-xl font-bold text-gray-900">$${(product.price || 0).toFixed(2)}</span>
|
| 435 |
-
${product.original_price ? `
|
| 436 |
-
<span class="text-sm text-gray-400 line-through">$${product.original_price.toFixed(2)}</span>
|
| 437 |
-
<span class="bg-red-100 text-red-600 text-xs font-semibold px-2 py-1 rounded-full">-${discount}%</span>
|
| 438 |
-
` : ''}
|
| 439 |
-
</div>
|
| 440 |
-
|
| 441 |
-
<!-- Quantity Controls -->
|
| 442 |
-
<div class="flex items-center justify-between mt-4">
|
| 443 |
-
<div class="flex items-center bg-gray-100 rounded-xl p-1">
|
| 444 |
-
<button onclick="updateQuantity('${productId}', ${quantity - 1}, '${cartItemId}')"
|
| 445 |
-
class="quantity-btn w-10 h-10 flex items-center justify-center rounded-lg bg-white shadow-sm hover:bg-gray-50 ${quantity <= 1 ? 'opacity-50 cursor-not-allowed' : ''}">
|
| 446 |
-
<i class="fas fa-minus text-gray-600"></i>
|
| 447 |
-
</button>
|
| 448 |
-
<span class="w-14 text-center font-semibold text-gray-900">${quantity}</span>
|
| 449 |
-
<button onclick="updateQuantity('${productId}', ${quantity + 1}, '${cartItemId}')"
|
| 450 |
-
class="quantity-btn w-10 h-10 flex items-center justify-center rounded-lg bg-white shadow-sm hover:bg-gray-50">
|
| 451 |
-
<i class="fas fa-plus text-gray-600"></i>
|
| 452 |
-
</button>
|
| 453 |
-
</div>
|
| 454 |
-
|
| 455 |
-
<div class="text-right">
|
| 456 |
-
<span class="text-sm text-gray-500">Subtotal</span>
|
| 457 |
-
<p class="text-lg font-bold text-brand-500">$${((product.price || 0) * quantity).toFixed(2)}</p>
|
| 458 |
-
</div>
|
| 459 |
-
</div>
|
| 460 |
-
</div>
|
| 461 |
-
</div>
|
| 462 |
-
</div>
|
| 463 |
-
`;
|
| 464 |
-
}
|
| 465 |
-
|
| 466 |
-
function renderOrderSummary(cartTotal, isLoggedIn) {
|
| 467 |
-
const shipping = cartTotal > 100 ? 0 : (cartTotal > 0 ? 10.00 : 0);
|
| 468 |
-
const tax = cartTotal * 0.1;
|
| 469 |
-
const total = cartTotal + shipping + tax;
|
| 470 |
-
|
| 471 |
-
return `
|
| 472 |
-
<div class="lg:col-span-1">
|
| 473 |
-
<div class="summary-card bg-white rounded-2xl shadow-sm p-6 sticky top-24">
|
| 474 |
-
<h2 class="text-xl font-bold text-gray-900 mb-6">Order Summary</h2>
|
| 475 |
-
|
| 476 |
-
<!-- Promo Code -->
|
| 477 |
-
<div class="mb-6">
|
| 478 |
-
<label class="text-sm font-medium text-gray-700 mb-2 block">Promo Code</label>
|
| 479 |
-
<div class="flex gap-2">
|
| 480 |
-
<input type="text" placeholder="Enter code"
|
| 481 |
-
class="promo-input flex-1 px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 transition-colors">
|
| 482 |
-
<button class="px-4 py-3 bg-dark-900 text-white rounded-xl font-medium hover:bg-dark-800 transition-colors">
|
| 483 |
-
Apply
|
| 484 |
-
</button>
|
| 485 |
-
</div>
|
| 486 |
-
</div>
|
| 487 |
-
|
| 488 |
-
<div class="space-y-4 border-t pt-4">
|
| 489 |
-
<div class="flex justify-between text-gray-600">
|
| 490 |
-
<span>Subtotal</span>
|
| 491 |
-
<span class="font-medium">$${cartTotal.toFixed(2)}</span>
|
| 492 |
-
</div>
|
| 493 |
-
<div class="flex justify-between text-gray-600">
|
| 494 |
-
<span>Shipping</span>
|
| 495 |
-
<span class="font-medium">${shipping > 0 ? '$' + shipping.toFixed(2) : 'Free'}</span>
|
| 496 |
-
</div>
|
| 497 |
-
<div class="flex justify-between text-gray-600">
|
| 498 |
-
<span>Tax (10%)</span>
|
| 499 |
-
<span class="font-medium">$${tax.toFixed(2)}</span>
|
| 500 |
-
</div>
|
| 501 |
-
|
| 502 |
-
${cartTotal > 100 ? `
|
| 503 |
-
<div class="bg-green-50 border border-green-200 rounded-xl p-3 text-sm text-green-700">
|
| 504 |
-
<i class="fas fa-check-circle mr-2"></i>
|
| 505 |
-
You're saving $10.00 on shipping!
|
| 506 |
-
</div>
|
| 507 |
-
` : ''}
|
| 508 |
-
</div>
|
| 509 |
-
|
| 510 |
-
<div class="border-t mt-4 pt-4">
|
| 511 |
-
<div class="flex justify-between items-center mb-6">
|
| 512 |
-
<span class="text-lg font-bold text-gray-900">Total</span>
|
| 513 |
-
<span class="text-2xl font-bold text-brand-500">$${total.toFixed(2)}</span>
|
| 514 |
-
</div>
|
| 515 |
-
|
| 516 |
-
<button onclick="proceedToCheckout()"
|
| 517 |
-
class="checkout-btn w-full py-4 text-white font-semibold rounded-xl flex items-center justify-center gap-2">
|
| 518 |
-
<i class="fas fa-lock"></i>
|
| 519 |
-
Proceed to Checkout
|
| 520 |
-
</button>
|
| 521 |
-
|
| 522 |
-
<p class="text-xs text-gray-500 text-center mt-4">
|
| 523 |
-
<i class="fas fa-shield-alt mr-1"></i>
|
| 524 |
-
Secure checkout powered by Stripe
|
| 525 |
-
</p>
|
| 526 |
-
</div>
|
| 527 |
-
|
| 528 |
-
<!-- Payment Methods -->
|
| 529 |
-
<div class="mt-6 pt-6 border-t">
|
| 530 |
-
<p class="text-xs text-gray-500 text-center mb-3">We accept</p>
|
| 531 |
-
<div class="flex justify-center gap-3 text-2xl text-gray-400">
|
| 532 |
-
<i class="fab fa-cc-visa"></i>
|
| 533 |
-
<i class="fab fa-cc-mastercard"></i>
|
| 534 |
-
<i class="fab fa-cc-amex"></i>
|
| 535 |
-
<i class="fab fa-cc-paypal"></i>
|
| 536 |
-
</div>
|
| 537 |
-
</div>
|
| 538 |
-
</div>
|
| 539 |
-
</div>
|
| 540 |
-
`;
|
| 541 |
-
}
|
| 542 |
-
|
| 543 |
-
function renderEmptyCart() {
|
| 544 |
-
document.getElementById('cartSummary').textContent = 'Your cart is empty';
|
| 545 |
-
|
| 546 |
-
document.getElementById('cartContent').innerHTML = `
|
| 547 |
-
<div class="bg-white rounded-2xl p-12 text-center shadow-sm max-w-2xl mx-auto fade-in">
|
| 548 |
-
<div class="empty-cart-icon w-32 h-32 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
| 549 |
-
<i class="fas fa-shopping-cart text-5xl text-gray-300"></i>
|
| 550 |
-
</div>
|
| 551 |
-
<h2 class="text-2xl font-bold text-gray-900 mb-2">Your cart is empty</h2>
|
| 552 |
-
<p class="text-gray-500 mb-8">Looks like you haven't added any items to your cart yet. Start shopping to fill it up!</p>
|
| 553 |
-
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
| 554 |
-
<a href="../index.html" class="px-8 py-3 bg-brand-500 text-white font-semibold rounded-xl hover:bg-brand-600 transition-colors">
|
| 555 |
-
<i class="fas fa-shopping-bag mr-2"></i>Start Shopping
|
| 556 |
-
</a>
|
| 557 |
-
</div>
|
| 558 |
-
</div>
|
| 559 |
-
`;
|
| 560 |
-
}
|
| 561 |
-
|
| 562 |
-
async function updateQuantity(productId, newQuantity, cartItemId) {
|
| 563 |
-
if (newQuantity < 1) {
|
| 564 |
-
removeItem(productId, cartItemId);
|
| 565 |
-
return;
|
| 566 |
-
}
|
| 567 |
-
|
| 568 |
-
if (!currentUser) {
|
| 569 |
-
window.location.href = 'login.html?redirect=cart';
|
| 570 |
-
return;
|
| 571 |
-
}
|
| 572 |
-
|
| 573 |
-
try {
|
| 574 |
-
const result = await window.Cart.updateQuantity(cartItemId, newQuantity);
|
| 575 |
-
|
| 576 |
-
if (!result.success) throw new Error(result.message || 'Failed to update quantity');
|
| 577 |
-
|
| 578 |
-
await loadCart();
|
| 579 |
-
} catch (error) {
|
| 580 |
-
console.error('Error updating quantity:', error);
|
| 581 |
-
alert('Failed to update quantity. Please try again.');
|
| 582 |
-
}
|
| 583 |
-
}
|
| 584 |
-
|
| 585 |
-
async function removeItem(productId, cartItemId) {
|
| 586 |
-
if (!confirm('Remove this item from your cart?')) return;
|
| 587 |
-
|
| 588 |
-
if (!currentUser) {
|
| 589 |
-
window.location.href = 'login.html?redirect=cart';
|
| 590 |
-
return;
|
| 591 |
-
}
|
| 592 |
-
|
| 593 |
-
try {
|
| 594 |
-
const result = await window.Cart.removeFromCart(cartItemId);
|
| 595 |
-
|
| 596 |
-
if (!result.success) throw new Error(result.message || 'Failed to remove item');
|
| 597 |
-
|
| 598 |
-
await loadCart();
|
| 599 |
-
} catch (error) {
|
| 600 |
-
console.error('Error removing item:', error);
|
| 601 |
-
alert('Failed to remove item. Please try again.');
|
| 602 |
-
}
|
| 603 |
-
}
|
| 604 |
-
|
| 605 |
-
async function proceedToCheckout() {
|
| 606 |
-
if (!currentUser) {
|
| 607 |
-
window.location.href = 'login.html?redirect=cart';
|
| 608 |
-
return;
|
| 609 |
-
}
|
| 610 |
-
|
| 611 |
-
try {
|
| 612 |
-
const cartItems = await window.Cart.getCartItems();
|
| 613 |
-
|
| 614 |
-
if (!cartItems || cartItems.length === 0) {
|
| 615 |
-
alert('Your cart is empty!');
|
| 616 |
-
return;
|
| 617 |
-
}
|
| 618 |
-
|
| 619 |
-
const items = cartItems.map(item => ({
|
| 620 |
-
product_id: item.product_id,
|
| 621 |
-
name: item.products?.name || '',
|
| 622 |
-
quantity: item.quantity,
|
| 623 |
-
price: item.products?.price || 0,
|
| 624 |
-
total: (item.products?.price || 0) * item.quantity
|
| 625 |
-
}));
|
| 626 |
-
|
| 627 |
-
const cartTotal = items.reduce((sum, item) => sum + item.total, 0);
|
| 628 |
-
const shipping = cartTotal > 100 ? 0 : 10.00;
|
| 629 |
-
const tax = cartTotal * 0.1;
|
| 630 |
-
const total = cartTotal + shipping + tax;
|
| 631 |
-
|
| 632 |
-
// Store in sessionStorage for checkout page (cleared after checkout)
|
| 633 |
-
sessionStorage.setItem('checkoutItems', JSON.stringify({
|
| 634 |
-
items,
|
| 635 |
-
subtotal: cartTotal,
|
| 636 |
-
shipping,
|
| 637 |
-
tax,
|
| 638 |
-
total,
|
| 639 |
-
userId: currentUser.id
|
| 640 |
-
}));
|
| 641 |
-
|
| 642 |
-
window.location.href = 'checkout.html';
|
| 643 |
-
|
| 644 |
-
} catch (error) {
|
| 645 |
-
console.error('Error proceeding to checkout:', error);
|
| 646 |
-
alert('There was an error processing your cart. Please try again.');
|
| 647 |
-
}
|
| 648 |
-
}
|
| 649 |
-
</script>
|
| 650 |
-
</body>
|
| 651 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/categories.html
DELETED
|
@@ -1,689 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Categories — Cellex</title>
|
| 7 |
-
<meta name="description" content="Browse all categories on Cellex - Nigeria's #1 Marketplace">
|
| 8 |
-
|
| 9 |
-
<!-- Fonts & Icons -->
|
| 10 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 11 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 12 |
-
|
| 13 |
-
<!-- Tailwind & Supabase -->
|
| 14 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 15 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 16 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 17 |
-
<script src="../js/cart.js?v=11"></script>
|
| 18 |
-
|
| 19 |
-
<script>
|
| 20 |
-
tailwind.config = {
|
| 21 |
-
theme: {
|
| 22 |
-
extend: {
|
| 23 |
-
fontFamily: {
|
| 24 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 25 |
-
},
|
| 26 |
-
colors: {
|
| 27 |
-
brand: {
|
| 28 |
-
50: '#fffbeb',
|
| 29 |
-
100: '#fef3c7',
|
| 30 |
-
200: '#fde68a',
|
| 31 |
-
300: '#fcd34d',
|
| 32 |
-
400: '#fbbf24',
|
| 33 |
-
500: '#f59e0b',
|
| 34 |
-
600: '#d97706',
|
| 35 |
-
700: '#b45309',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
|
| 43 |
-
<style>
|
| 44 |
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 45 |
-
html { scroll-behavior: smooth; }
|
| 46 |
-
body {
|
| 47 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 48 |
-
background: #ffffff;
|
| 49 |
-
color: #1f2937;
|
| 50 |
-
}
|
| 51 |
-
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 52 |
-
::-webkit-scrollbar-track { background: #f5f5f5; }
|
| 53 |
-
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
|
| 54 |
-
::-webkit-scrollbar-thumb:hover { background: #9ca3af; }
|
| 55 |
-
|
| 56 |
-
.glass {
|
| 57 |
-
background: rgba(255, 255, 255, 0.95);
|
| 58 |
-
backdrop-filter: blur(20px);
|
| 59 |
-
-webkit-backdrop-filter: blur(20px);
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
.category-card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0,0,0,0.1); }
|
| 63 |
-
.category-card { transition: all 0.3s ease; }
|
| 64 |
-
|
| 65 |
-
.product-card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0,0,0,0.1); }
|
| 66 |
-
.product-card { transition: all 0.3s ease; }
|
| 67 |
-
|
| 68 |
-
.filter-btn.active { background: #f97316; color: white; border-color: #f97316; }
|
| 69 |
-
</style>
|
| 70 |
-
</head>
|
| 71 |
-
|
| 72 |
-
<body class="min-h-screen pb-20 lg:pb-0">
|
| 73 |
-
<!-- Header -->
|
| 74 |
-
<header class="glass sticky top-0 z-50 border-b border-gray-100">
|
| 75 |
-
<div class="max-w-7xl mx-auto px-3 lg:px-4">
|
| 76 |
-
<div class="flex items-center justify-between h-14 lg:h-16">
|
| 77 |
-
<!-- Logo -->
|
| 78 |
-
<a href="../index.html" class="flex items-center gap-2">
|
| 79 |
-
<div class="w-8 h-8 lg:w-10 lg:h-10 flex items-center justify-center">
|
| 80 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 81 |
-
<defs>
|
| 82 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 83 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 84 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 85 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 86 |
-
</linearGradient>
|
| 87 |
-
</defs>
|
| 88 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z" fill="url(#headerLogoGrad)"/>
|
| 89 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11" fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 90 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48" fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 91 |
-
</svg>
|
| 92 |
-
</div>
|
| 93 |
-
<span class="text-base lg:text-lg font-bold text-gray-900">Cellex</span>
|
| 94 |
-
</a>
|
| 95 |
-
|
| 96 |
-
<!-- Search Bar -->
|
| 97 |
-
<div class="flex-1 max-w-xl mx-4 hidden md:flex items-center bg-gray-100 rounded-lg px-3 py-2">
|
| 98 |
-
<i class="fas fa-search text-gray-400 mr-2 text-sm"></i>
|
| 99 |
-
<input type="text" id="searchInput" placeholder="Search products..." class="flex-1 bg-transparent outline-none text-sm">
|
| 100 |
-
</div>
|
| 101 |
-
|
| 102 |
-
<!-- Right Actions -->
|
| 103 |
-
<div class="flex items-center gap-2 lg:gap-4">
|
| 104 |
-
<a href="cart.html" class="relative p-2 hover:bg-gray-100 rounded-lg">
|
| 105 |
-
<i class="fas fa-shopping-cart text-gray-600"></i>
|
| 106 |
-
<span id="cartCount" class="absolute -top-0.5 -right-0.5 w-4 h-4 bg-red-500 text-white text-[10px] font-bold rounded-full flex items-center justify-center">0</span>
|
| 107 |
-
</a>
|
| 108 |
-
<a id="accountBtn" href="login.html" class="p-2 hover:bg-gray-100 rounded-lg flex items-center gap-1">
|
| 109 |
-
<div class="relative">
|
| 110 |
-
<i class="fas fa-user text-gray-600"></i>
|
| 111 |
-
<i id="loggedInCheck" class="fas fa-check-circle text-green-500 text-[8px] absolute -bottom-0.5 -right-0.5 hidden"></i>
|
| 112 |
-
</div>
|
| 113 |
-
<span class="hidden lg:inline text-sm text-gray-700">Account</span>
|
| 114 |
-
</a>
|
| 115 |
-
</div>
|
| 116 |
-
</div>
|
| 117 |
-
</div>
|
| 118 |
-
</header>
|
| 119 |
-
|
| 120 |
-
<!-- Main Content -->
|
| 121 |
-
<main class="max-w-7xl mx-auto px-3 lg:px-4 py-4 lg:py-6">
|
| 122 |
-
<!-- Page Title -->
|
| 123 |
-
<div class="mb-4 lg:mb-6">
|
| 124 |
-
<h1 class="text-xl lg:text-2xl font-bold text-gray-900">All Categories</h1>
|
| 125 |
-
<p class="text-gray-500 text-xs lg:text-sm mt-1">Browse products by category</p>
|
| 126 |
-
</div>
|
| 127 |
-
|
| 128 |
-
<!-- Categories Grid -->
|
| 129 |
-
<section class="mb-6 lg:mb-8">
|
| 130 |
-
<div class="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-6 gap-2 lg:gap-4" id="categoriesGrid">
|
| 131 |
-
<!-- All Categories -->
|
| 132 |
-
<button onclick="filterByCategory('')" class="category-card filter-btn active bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 133 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 134 |
-
<i class="fas fa-th-large text-gray-600 text-lg lg:text-xl"></i>
|
| 135 |
-
</div>
|
| 136 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">All</p>
|
| 137 |
-
</button>
|
| 138 |
-
<!-- Phones -->
|
| 139 |
-
<button onclick="filterByCategory('phones')" data-category="phones" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 140 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 141 |
-
<i class="fas fa-mobile-alt text-gray-600 text-lg lg:text-xl"></i>
|
| 142 |
-
</div>
|
| 143 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Phones</p>
|
| 144 |
-
</button>
|
| 145 |
-
<!-- Electronics -->
|
| 146 |
-
<button onclick="filterByCategory('electronics')" data-category="electronics" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 147 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 148 |
-
<i class="fas fa-laptop text-gray-600 text-lg lg:text-xl"></i>
|
| 149 |
-
</div>
|
| 150 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Electronics</p>
|
| 151 |
-
</button>
|
| 152 |
-
<!-- Fashion -->
|
| 153 |
-
<button onclick="filterByCategory('fashion')" data-category="fashion" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 154 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 155 |
-
<i class="fas fa-tshirt text-gray-600 text-lg lg:text-xl"></i>
|
| 156 |
-
</div>
|
| 157 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Fashion</p>
|
| 158 |
-
</button>
|
| 159 |
-
<!-- Farm Fresh -->
|
| 160 |
-
<button onclick="filterByCategory('farm')" data-category="farm" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 161 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 162 |
-
<i class="fas fa-leaf text-gray-600 text-lg lg:text-xl"></i>
|
| 163 |
-
</div>
|
| 164 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Farm Fresh</p>
|
| 165 |
-
</button>
|
| 166 |
-
<!-- Home -->
|
| 167 |
-
<button onclick="filterByCategory('home')" data-category="home" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 168 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 169 |
-
<i class="fas fa-home text-gray-600 text-lg lg:text-xl"></i>
|
| 170 |
-
</div>
|
| 171 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Home</p>
|
| 172 |
-
</button>
|
| 173 |
-
<!-- Beauty -->
|
| 174 |
-
<button onclick="filterByCategory('beauty')" data-category="beauty" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 175 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 176 |
-
<i class="fas fa-heart text-gray-600 text-lg lg:text-xl"></i>
|
| 177 |
-
</div>
|
| 178 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Beauty</p>
|
| 179 |
-
</button>
|
| 180 |
-
<!-- Baby -->
|
| 181 |
-
<button onclick="filterByCategory('baby')" data-category="baby" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 182 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 183 |
-
<i class="fas fa-baby text-gray-600 text-lg lg:text-xl"></i>
|
| 184 |
-
</div>
|
| 185 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Baby</p>
|
| 186 |
-
</button>
|
| 187 |
-
<!-- Sports -->
|
| 188 |
-
<button onclick="filterByCategory('sports')" data-category="sports" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 189 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 190 |
-
<i class="fas fa-futbol text-gray-600 text-lg lg:text-xl"></i>
|
| 191 |
-
</div>
|
| 192 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Sports</p>
|
| 193 |
-
</button>
|
| 194 |
-
<!-- Groceries -->
|
| 195 |
-
<button onclick="filterByCategory('groceries')" data-category="groceries" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 196 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 197 |
-
<i class="fas fa-shopping-basket text-gray-600 text-lg lg:text-xl"></i>
|
| 198 |
-
</div>
|
| 199 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Groceries</p>
|
| 200 |
-
</button>
|
| 201 |
-
<!-- Health -->
|
| 202 |
-
<button onclick="filterByCategory('health')" data-category="health" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 203 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 204 |
-
<i class="fas fa-pills text-gray-600 text-lg lg:text-xl"></i>
|
| 205 |
-
</div>
|
| 206 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Health</p>
|
| 207 |
-
</button>
|
| 208 |
-
<!-- Automotive -->
|
| 209 |
-
<button onclick="filterByCategory('automotive')" data-category="automotive" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 210 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 211 |
-
<i class="fas fa-car text-gray-600 text-lg lg:text-xl"></i>
|
| 212 |
-
</div>
|
| 213 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Automotive</p>
|
| 214 |
-
</button>
|
| 215 |
-
</div>
|
| 216 |
-
</section>
|
| 217 |
-
|
| 218 |
-
<!-- Filters Bar -->
|
| 219 |
-
<section class="bg-gray-50 rounded-xl p-3 lg:p-4 mb-4 lg:mb-6">
|
| 220 |
-
<div class="flex flex-wrap items-center gap-2 lg:gap-4">
|
| 221 |
-
<!-- Sort -->
|
| 222 |
-
<div class="flex items-center gap-2">
|
| 223 |
-
<span class="text-xs lg:text-sm text-gray-500">Sort:</span>
|
| 224 |
-
<select id="sortSelect" onchange="applyFilters()" class="text-xs lg:text-sm border border-gray-200 rounded-lg px-2 lg:px-3 py-1.5 lg:py-2 bg-white">
|
| 225 |
-
<option value="newest">Newest</option>
|
| 226 |
-
<option value="popular">Popular</option>
|
| 227 |
-
<option value="price-low">Price: Low to High</option>
|
| 228 |
-
<option value="price-high">Price: High to Low</option>
|
| 229 |
-
</select>
|
| 230 |
-
</div>
|
| 231 |
-
<!-- Price Range -->
|
| 232 |
-
<div class="flex items-center gap-2">
|
| 233 |
-
<span class="text-xs lg:text-sm text-gray-500">Price:</span>
|
| 234 |
-
<select id="priceSelect" onchange="applyFilters()" class="text-xs lg:text-sm border border-gray-200 rounded-lg px-2 lg:px-3 py-1.5 lg:py-2 bg-white">
|
| 235 |
-
<option value="">All Prices</option>
|
| 236 |
-
<option value="0-5000">Under ₦5,000</option>
|
| 237 |
-
<option value="5000-10000">₦5,000 - ₦10,000</option>
|
| 238 |
-
<option value="10000-50000">₦10,000 - ₦50,000</option>
|
| 239 |
-
<option value="50000+">₦50,000+</option>
|
| 240 |
-
</select>
|
| 241 |
-
</div>
|
| 242 |
-
<!-- Seller Type -->
|
| 243 |
-
<div class="flex items-center gap-2">
|
| 244 |
-
<span class="text-xs lg:text-sm text-gray-500">Seller:</span>
|
| 245 |
-
<select id="sellerSelect" onchange="applyFilters()" class="text-xs lg:text-sm border border-gray-200 rounded-lg px-2 lg:px-3 py-1.5 lg:py-2 bg-white">
|
| 246 |
-
<option value="">All Sellers</option>
|
| 247 |
-
<option value="merchant">Merchants</option>
|
| 248 |
-
<option value="farmer">Farmers</option>
|
| 249 |
-
</select>
|
| 250 |
-
</div>
|
| 251 |
-
<!-- Results Count -->
|
| 252 |
-
<div class="ml-auto text-xs lg:text-sm text-gray-500">
|
| 253 |
-
<span id="resultsCount">0</span> products found
|
| 254 |
-
</div>
|
| 255 |
-
</div>
|
| 256 |
-
</section>
|
| 257 |
-
|
| 258 |
-
<!-- Products Grid -->
|
| 259 |
-
<section>
|
| 260 |
-
<div id="productsGrid" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2 lg:gap-4">
|
| 261 |
-
<!-- Products loaded dynamically -->
|
| 262 |
-
</div>
|
| 263 |
-
|
| 264 |
-
<!-- Loading State -->
|
| 265 |
-
<div id="loadingState" class="text-center py-12 hidden">
|
| 266 |
-
<i class="fas fa-spinner fa-spin text-orange-500 text-2xl mb-3"></i>
|
| 267 |
-
<p class="text-gray-500 text-sm">Loading products...</p>
|
| 268 |
-
</div>
|
| 269 |
-
|
| 270 |
-
<!-- Empty State -->
|
| 271 |
-
<div id="emptyState" class="text-center py-12 hidden">
|
| 272 |
-
<i class="fas fa-box-open text-gray-300 text-4xl mb-3"></i>
|
| 273 |
-
<p class="text-gray-500 text-sm">No products found in this category</p>
|
| 274 |
-
<button onclick="filterByCategory('')" class="mt-3 text-orange-500 text-sm font-medium hover:underline">View all products</button>
|
| 275 |
-
</div>
|
| 276 |
-
|
| 277 |
-
<!-- Load More -->
|
| 278 |
-
<div id="loadMoreSection" class="text-center mt-6 lg:mt-8 hidden">
|
| 279 |
-
<button onclick="loadMoreProducts()" class="bg-gray-100 text-gray-700 px-6 py-2 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
|
| 280 |
-
Load More Products
|
| 281 |
-
</button>
|
| 282 |
-
</div>
|
| 283 |
-
</section>
|
| 284 |
-
</main>
|
| 285 |
-
|
| 286 |
-
<!-- Mobile Navigation (Bottom) -->
|
| 287 |
-
<nav class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 lg:hidden z-50">
|
| 288 |
-
<div class="flex items-center justify-around py-2">
|
| 289 |
-
<a href="../index.html" class="flex flex-col items-center gap-1 p-2 text-gray-500">
|
| 290 |
-
<i class="fas fa-home text-lg"></i>
|
| 291 |
-
<span class="text-xs">Home</span>
|
| 292 |
-
</a>
|
| 293 |
-
<a href="categories.html" class="flex flex-col items-center gap-1 p-2 text-orange-500">
|
| 294 |
-
<i class="fas fa-th-large text-lg"></i>
|
| 295 |
-
<span class="text-xs font-medium">Categories</span>
|
| 296 |
-
</a>
|
| 297 |
-
<a href="#" class="flex flex-col items-center gap-1 p-2 text-gray-500">
|
| 298 |
-
<i class="fas fa-bolt text-lg"></i>
|
| 299 |
-
<span class="text-xs">Deals</span>
|
| 300 |
-
</a>
|
| 301 |
-
<a href="cart.html" class="flex flex-col items-center gap-1 p-2 text-gray-500 relative">
|
| 302 |
-
<i class="fas fa-shopping-cart text-lg"></i>
|
| 303 |
-
<span class="text-xs">Cart</span>
|
| 304 |
-
<span id="mobileCartCount" class="absolute top-0 right-1 w-4 h-4 bg-red-500 text-white text-[10px] font-bold rounded-full flex items-center justify-center">0</span>
|
| 305 |
-
</a>
|
| 306 |
-
<a id="mobileAccountBtn" href="login.html" class="flex flex-col items-center gap-1 p-2 text-gray-500">
|
| 307 |
-
<div class="relative">
|
| 308 |
-
<i class="fas fa-user text-lg"></i>
|
| 309 |
-
<i id="mobileLoggedInCheck" class="fas fa-check-circle text-green-500 text-[8px] absolute -bottom-0.5 -right-0.5 hidden"></i>
|
| 310 |
-
</div>
|
| 311 |
-
<span class="text-xs">Account</span>
|
| 312 |
-
</a>
|
| 313 |
-
</div>
|
| 314 |
-
</nav>
|
| 315 |
-
|
| 316 |
-
<!-- Auth Modal -->
|
| 317 |
-
<div id="authModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden items-center justify-center p-4">
|
| 318 |
-
<div class="bg-white rounded-2xl max-w-sm w-full p-6 text-center">
|
| 319 |
-
<div class="w-16 h-16 bg-orange-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
| 320 |
-
<i class="fas fa-user-lock text-orange-500 text-2xl"></i>
|
| 321 |
-
</div>
|
| 322 |
-
<h3 class="text-lg font-bold text-gray-900 mb-2">Sign In Required</h3>
|
| 323 |
-
<p class="text-gray-500 text-sm mb-4">Please log in to add items to your cart</p>
|
| 324 |
-
<div class="flex gap-3">
|
| 325 |
-
<a href="login.html" class="flex-1 bg-gray-900 text-white py-2.5 rounded-lg font-medium text-sm hover:bg-gray-800 transition-colors">
|
| 326 |
-
Login
|
| 327 |
-
</a>
|
| 328 |
-
<a href="signup.html" class="flex-1 bg-orange-500 text-white py-2.5 rounded-lg font-medium text-sm hover:bg-orange-600 transition-colors">
|
| 329 |
-
Sign Up
|
| 330 |
-
</a>
|
| 331 |
-
</div>
|
| 332 |
-
<button onclick="closeAuthModal()" class="mt-3 text-gray-500 text-sm hover:text-gray-700">Continue Browsing</button>
|
| 333 |
-
</div>
|
| 334 |
-
</div>
|
| 335 |
-
|
| 336 |
-
<!-- Scripts -->
|
| 337 |
-
<script>
|
| 338 |
-
// Edge function client + Cart module are loaded via <script> tags above.
|
| 339 |
-
// Use window.EdgeFunctions and window.Cart instead of supabaseClient.
|
| 340 |
-
let currentCategory = '';
|
| 341 |
-
let currentSearch = '';
|
| 342 |
-
let currentSort = 'newest';
|
| 343 |
-
let currentPrice = '';
|
| 344 |
-
let currentSeller = '';
|
| 345 |
-
let products = [];
|
| 346 |
-
let page = 0;
|
| 347 |
-
const pageSize = 20;
|
| 348 |
-
|
| 349 |
-
// Initialize
|
| 350 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 351 |
-
checkAuthState();
|
| 352 |
-
|
| 353 |
-
// Get parameters from URL
|
| 354 |
-
const params = new URLSearchParams(window.location.search);
|
| 355 |
-
const cat = params.get('category');
|
| 356 |
-
const search = params.get('search');
|
| 357 |
-
|
| 358 |
-
// Set search input value if search param exists
|
| 359 |
-
if (search) {
|
| 360 |
-
currentSearch = search;
|
| 361 |
-
const searchInput = document.getElementById('searchInput');
|
| 362 |
-
if (searchInput) searchInput.value = search;
|
| 363 |
-
updatePageTitle(search, '');
|
| 364 |
-
}
|
| 365 |
-
|
| 366 |
-
if (cat) {
|
| 367 |
-
filterByCategory(cat);
|
| 368 |
-
} else {
|
| 369 |
-
loadProducts();
|
| 370 |
-
}
|
| 371 |
-
});
|
| 372 |
-
|
| 373 |
-
// Update page title based on filters
|
| 374 |
-
function updatePageTitle(search, category) {
|
| 375 |
-
const titleEl = document.querySelector('main h1');
|
| 376 |
-
const descEl = document.querySelector('main h1 + p');
|
| 377 |
-
|
| 378 |
-
if (search && category) {
|
| 379 |
-
titleEl.textContent = `"${search}" in ${category}`;
|
| 380 |
-
descEl.textContent = 'Search results';
|
| 381 |
-
} else if (search) {
|
| 382 |
-
titleEl.textContent = `Search: "${search}"`;
|
| 383 |
-
descEl.textContent = 'Showing search results';
|
| 384 |
-
} else if (category) {
|
| 385 |
-
titleEl.textContent = category.charAt(0).toUpperCase() + category.slice(1);
|
| 386 |
-
descEl.textContent = 'Browse products in this category';
|
| 387 |
-
} else {
|
| 388 |
-
titleEl.textContent = 'All Categories';
|
| 389 |
-
descEl.textContent = 'Browse products by category';
|
| 390 |
-
}
|
| 391 |
-
}
|
| 392 |
-
|
| 393 |
-
// Auth State
|
| 394 |
-
async function checkAuthState() {
|
| 395 |
-
try {
|
| 396 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 397 |
-
const accountBtn = document.getElementById('accountBtn');
|
| 398 |
-
const mobileAccountBtn = document.getElementById('mobileAccountBtn');
|
| 399 |
-
const loggedInCheck = document.getElementById('loggedInCheck');
|
| 400 |
-
const mobileLoggedInCheck = document.getElementById('mobileLoggedInCheck');
|
| 401 |
-
|
| 402 |
-
if (user) {
|
| 403 |
-
accountBtn.href = 'profile.html';
|
| 404 |
-
mobileAccountBtn.href = 'profile.html';
|
| 405 |
-
loggedInCheck.classList.remove('hidden');
|
| 406 |
-
mobileLoggedInCheck.classList.remove('hidden');
|
| 407 |
-
}
|
| 408 |
-
} catch (error) {
|
| 409 |
-
console.error('Auth check error:', error);
|
| 410 |
-
}
|
| 411 |
-
}
|
| 412 |
-
|
| 413 |
-
// Filter by Category
|
| 414 |
-
function filterByCategory(category) {
|
| 415 |
-
currentCategory = category;
|
| 416 |
-
page = 0;
|
| 417 |
-
products = [];
|
| 418 |
-
|
| 419 |
-
// Clear search when changing category
|
| 420 |
-
currentSearch = '';
|
| 421 |
-
const searchInput = document.getElementById('searchInput');
|
| 422 |
-
if (searchInput) searchInput.value = '';
|
| 423 |
-
|
| 424 |
-
// Update active button
|
| 425 |
-
document.querySelectorAll('.filter-btn').forEach(btn => {
|
| 426 |
-
btn.classList.remove('active');
|
| 427 |
-
if (btn.dataset.category === category || (category === '' && !btn.dataset.category)) {
|
| 428 |
-
btn.classList.add('active');
|
| 429 |
-
}
|
| 430 |
-
});
|
| 431 |
-
|
| 432 |
-
// Update URL
|
| 433 |
-
const url = new URL(window.location);
|
| 434 |
-
if (category) {
|
| 435 |
-
url.searchParams.set('category', category);
|
| 436 |
-
} else {
|
| 437 |
-
url.searchParams.delete('category');
|
| 438 |
-
}
|
| 439 |
-
url.searchParams.delete('search');
|
| 440 |
-
window.history.replaceState({}, '', url);
|
| 441 |
-
|
| 442 |
-
// Update page title
|
| 443 |
-
updatePageTitle('', category);
|
| 444 |
-
|
| 445 |
-
loadProducts();
|
| 446 |
-
}
|
| 447 |
-
|
| 448 |
-
// Apply Filters
|
| 449 |
-
function applyFilters() {
|
| 450 |
-
currentSort = document.getElementById('sortSelect').value;
|
| 451 |
-
currentPrice = document.getElementById('priceSelect').value;
|
| 452 |
-
currentSeller = document.getElementById('sellerSelect').value;
|
| 453 |
-
page = 0;
|
| 454 |
-
products = [];
|
| 455 |
-
loadProducts();
|
| 456 |
-
}
|
| 457 |
-
|
| 458 |
-
// Load Products
|
| 459 |
-
async function loadProducts() {
|
| 460 |
-
const loadingState = document.getElementById('loadingState');
|
| 461 |
-
const emptyState = document.getElementById('emptyState');
|
| 462 |
-
const productsGrid = document.getElementById('productsGrid');
|
| 463 |
-
|
| 464 |
-
loadingState.classList.remove('hidden');
|
| 465 |
-
emptyState.classList.add('hidden');
|
| 466 |
-
|
| 467 |
-
try {
|
| 468 |
-
let result;
|
| 469 |
-
// Determine max price filter (the edge function search supports a single maxPrice)
|
| 470 |
-
let maxPrice = null;
|
| 471 |
-
if (currentPrice) {
|
| 472 |
-
if (currentPrice === '50000+') {
|
| 473 |
-
maxPrice = 50000;
|
| 474 |
-
} else {
|
| 475 |
-
const [min, max] = currentPrice.split('-');
|
| 476 |
-
maxPrice = parseInt(max);
|
| 477 |
-
}
|
| 478 |
-
}
|
| 479 |
-
|
| 480 |
-
if (currentSearch) {
|
| 481 |
-
// Search within all products (optionally capped by maxPrice)
|
| 482 |
-
result = await window.EdgeFunctions.products.search(currentSearch, maxPrice);
|
| 483 |
-
} else if (currentCategory) {
|
| 484 |
-
// Browse by category with sort + pagination
|
| 485 |
-
result = await window.EdgeFunctions.products.category(currentCategory, currentSort, page + 1);
|
| 486 |
-
} else {
|
| 487 |
-
// No category selected — load all products
|
| 488 |
-
result = await window.EdgeFunctions.products.all(50);
|
| 489 |
-
}
|
| 490 |
-
|
| 491 |
-
if (!result.success) {
|
| 492 |
-
throw new Error(result.error || 'Failed to load products');
|
| 493 |
-
}
|
| 494 |
-
|
| 495 |
-
const data = result.products || [];
|
| 496 |
-
const count = result.count || data.length;
|
| 497 |
-
|
| 498 |
-
document.getElementById('resultsCount').textContent = count;
|
| 499 |
-
|
| 500 |
-
if (data && data.length > 0) {
|
| 501 |
-
products = [...products, ...data];
|
| 502 |
-
renderProducts(data);
|
| 503 |
-
|
| 504 |
-
// Show load more if there are more products
|
| 505 |
-
const loadMoreSection = document.getElementById('loadMoreSection');
|
| 506 |
-
if (products.length < count) {
|
| 507 |
-
loadMoreSection.classList.remove('hidden');
|
| 508 |
-
} else {
|
| 509 |
-
loadMoreSection.classList.add('hidden');
|
| 510 |
-
}
|
| 511 |
-
} else if (products.length === 0) {
|
| 512 |
-
emptyState.classList.remove('hidden');
|
| 513 |
-
productsGrid.innerHTML = '';
|
| 514 |
-
}
|
| 515 |
-
|
| 516 |
-
} catch (error) {
|
| 517 |
-
console.error('Error loading products:', error);
|
| 518 |
-
emptyState.classList.remove('hidden');
|
| 519 |
-
} finally {
|
| 520 |
-
loadingState.classList.add('hidden');
|
| 521 |
-
}
|
| 522 |
-
}
|
| 523 |
-
|
| 524 |
-
// Render Products
|
| 525 |
-
function renderProducts(data) {
|
| 526 |
-
const productsGrid = document.getElementById('productsGrid');
|
| 527 |
-
|
| 528 |
-
if (page === 0) {
|
| 529 |
-
productsGrid.innerHTML = '';
|
| 530 |
-
}
|
| 531 |
-
|
| 532 |
-
data.forEach(product => {
|
| 533 |
-
const card = document.createElement('div');
|
| 534 |
-
card.className = 'product-card bg-white rounded-xl border border-gray-100 overflow-hidden cursor-pointer';
|
| 535 |
-
card.onclick = () => window.location.href = `product.html?id=${product.id}`;
|
| 536 |
-
|
| 537 |
-
const sellerType = product.sellers?.seller_type;
|
| 538 |
-
const sellerBadge = sellerType === 'farmer'
|
| 539 |
-
? '<span class="bg-green-100 text-green-700 text-[10px] px-1.5 py-0.5 rounded-full">Farm Fresh</span>'
|
| 540 |
-
: '';
|
| 541 |
-
|
| 542 |
-
const discount = product.discount_percentage || 0;
|
| 543 |
-
const originalPrice = discount > 0 ? product.price / (1 - discount/100) : null;
|
| 544 |
-
|
| 545 |
-
card.innerHTML = `
|
| 546 |
-
<div class="relative">
|
| 547 |
-
<img src="${product.images?.[0] || 'https://via.placeholder.com/300'}"
|
| 548 |
-
alt="${product.name}"
|
| 549 |
-
class="w-full h-28 lg:h-40 object-cover">
|
| 550 |
-
${discount > 0 ? `<span class="absolute top-2 left-2 bg-red-500 text-white text-[10px] px-1.5 py-0.5 rounded-full">-${discount}%</span>` : ''}
|
| 551 |
-
<button onclick="event.stopPropagation(); toggleWishlist(${product.id})" class="absolute top-2 right-2 w-7 h-7 bg-white rounded-full flex items-center justify-center shadow hover:bg-gray-50">
|
| 552 |
-
<i class="far fa-heart text-gray-400 text-xs"></i>
|
| 553 |
-
</button>
|
| 554 |
-
</div>
|
| 555 |
-
<div class="p-2 lg:p-3">
|
| 556 |
-
<div class="flex items-center gap-1 mb-1">
|
| 557 |
-
${sellerBadge}
|
| 558 |
-
</div>
|
| 559 |
-
<h3 class="text-xs lg:text-sm font-medium text-gray-900 line-clamp-2 mb-1">${product.name}</h3>
|
| 560 |
-
<div class="flex items-center gap-1 mb-2">
|
| 561 |
-
<span class="text-sm lg:text-base font-bold text-gray-900">₦${product.price?.toLocaleString()}</span>
|
| 562 |
-
${originalPrice ? `<span class="text-[10px] text-gray-400 line-through">₦${originalPrice.toFixed(0)}</span>` : ''}
|
| 563 |
-
</div>
|
| 564 |
-
<button onclick="event.stopPropagation(); addToCart(${product.id})" class="w-full bg-orange-500 text-white py-1.5 lg:py-2 rounded-lg text-xs font-medium hover:bg-orange-600 transition-colors">
|
| 565 |
-
Add to Cart
|
| 566 |
-
</button>
|
| 567 |
-
</div>
|
| 568 |
-
`;
|
| 569 |
-
|
| 570 |
-
productsGrid.appendChild(card);
|
| 571 |
-
});
|
| 572 |
-
}
|
| 573 |
-
|
| 574 |
-
// Load More Products
|
| 575 |
-
function loadMoreProducts() {
|
| 576 |
-
page++;
|
| 577 |
-
loadProducts();
|
| 578 |
-
}
|
| 579 |
-
|
| 580 |
-
// Add to Cart
|
| 581 |
-
async function addToCart(productId) {
|
| 582 |
-
try {
|
| 583 |
-
const result = await window.Cart.addToCart(productId, 1);
|
| 584 |
-
|
| 585 |
-
if (!result.success) {
|
| 586 |
-
if (result.requiresAuth) {
|
| 587 |
-
document.getElementById('authModal').classList.remove('hidden');
|
| 588 |
-
document.getElementById('authModal').classList.add('flex');
|
| 589 |
-
return;
|
| 590 |
-
}
|
| 591 |
-
showNotification(result.message || 'Error adding to cart', 'error');
|
| 592 |
-
return;
|
| 593 |
-
}
|
| 594 |
-
|
| 595 |
-
showNotification('Added to cart!');
|
| 596 |
-
|
| 597 |
-
} catch (error) {
|
| 598 |
-
console.error('Add to cart error:', error);
|
| 599 |
-
showNotification('Error adding to cart', 'error');
|
| 600 |
-
}
|
| 601 |
-
}
|
| 602 |
-
|
| 603 |
-
// Toggle Wishlist
|
| 604 |
-
async function toggleWishlist(productId) {
|
| 605 |
-
try {
|
| 606 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 607 |
-
|
| 608 |
-
if (!user) {
|
| 609 |
-
document.getElementById('authModal').classList.remove('hidden');
|
| 610 |
-
document.getElementById('authModal').classList.add('flex');
|
| 611 |
-
return;
|
| 612 |
-
}
|
| 613 |
-
|
| 614 |
-
// Toggle wishlist logic here
|
| 615 |
-
showNotification('Added to wishlist!');
|
| 616 |
-
|
| 617 |
-
} catch (error) {
|
| 618 |
-
console.error('Wishlist error:', error);
|
| 619 |
-
}
|
| 620 |
-
}
|
| 621 |
-
|
| 622 |
-
// Update Cart Count
|
| 623 |
-
async function updateCartCount() {
|
| 624 |
-
try {
|
| 625 |
-
// Cart module handles auth check + DOM updates internally
|
| 626 |
-
await window.Cart.updateCartCountUI();
|
| 627 |
-
} catch (error) {
|
| 628 |
-
console.error('Cart count error:', error);
|
| 629 |
-
}
|
| 630 |
-
}
|
| 631 |
-
|
| 632 |
-
// Close Auth Modal
|
| 633 |
-
function closeAuthModal() {
|
| 634 |
-
document.getElementById('authModal').classList.add('hidden');
|
| 635 |
-
document.getElementById('authModal').classList.remove('flex');
|
| 636 |
-
}
|
| 637 |
-
|
| 638 |
-
// Show Notification
|
| 639 |
-
function showNotification(message, type = 'success') {
|
| 640 |
-
const notification = document.createElement('div');
|
| 641 |
-
notification.className = `fixed top-20 right-4 px-4 py-2 rounded-lg text-sm font-medium z-50 ${type === 'success' ? 'bg-green-500 text-white' : 'bg-red-500 text-white'}`;
|
| 642 |
-
notification.textContent = message;
|
| 643 |
-
document.body.appendChild(notification);
|
| 644 |
-
|
| 645 |
-
setTimeout(() => notification.remove(), 2000);
|
| 646 |
-
}
|
| 647 |
-
|
| 648 |
-
// ===== Search Functions =====
|
| 649 |
-
function handleSearchKeypress(event) {
|
| 650 |
-
if (event.key === 'Enter') {
|
| 651 |
-
performSearch();
|
| 652 |
-
}
|
| 653 |
-
}
|
| 654 |
-
|
| 655 |
-
function performSearch() {
|
| 656 |
-
const searchInput = document.getElementById('searchInput');
|
| 657 |
-
const query = searchInput.value.trim();
|
| 658 |
-
|
| 659 |
-
currentSearch = query;
|
| 660 |
-
currentCategory = '';
|
| 661 |
-
page = 0;
|
| 662 |
-
products = [];
|
| 663 |
-
|
| 664 |
-
// Update URL
|
| 665 |
-
const url = new URL(window.location);
|
| 666 |
-
if (query) {
|
| 667 |
-
url.searchParams.set('search', query);
|
| 668 |
-
} else {
|
| 669 |
-
url.searchParams.delete('search');
|
| 670 |
-
}
|
| 671 |
-
url.searchParams.delete('category');
|
| 672 |
-
window.history.replaceState({}, '', url);
|
| 673 |
-
|
| 674 |
-
// Update title
|
| 675 |
-
updatePageTitle(query, '');
|
| 676 |
-
|
| 677 |
-
// Reset category buttons
|
| 678 |
-
document.querySelectorAll('.filter-btn').forEach(btn => {
|
| 679 |
-
btn.classList.remove('active');
|
| 680 |
-
if (!btn.dataset.category) {
|
| 681 |
-
btn.classList.add('active');
|
| 682 |
-
}
|
| 683 |
-
});
|
| 684 |
-
|
| 685 |
-
loadProducts();
|
| 686 |
-
}
|
| 687 |
-
</script>
|
| 688 |
-
</body>
|
| 689 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/checkout.html
DELETED
|
@@ -1,858 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Checkout - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* {
|
| 44 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
@keyframes float {
|
| 48 |
-
0% { transform: translateY(0px); }
|
| 49 |
-
50% { transform: translateY(-10px); }
|
| 50 |
-
100% { transform: translateY(0px); }
|
| 51 |
-
}
|
| 52 |
-
.animate-float {
|
| 53 |
-
animation: float 3s ease-in-out infinite;
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
@keyframes fadeInUp {
|
| 57 |
-
from { opacity: 0; transform: translateY(20px); }
|
| 58 |
-
to { opacity: 1; transform: translateY(0); }
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
@keyframes pulse {
|
| 62 |
-
0% { transform: scale(1); }
|
| 63 |
-
50% { transform: scale(1.05); }
|
| 64 |
-
100% { transform: scale(1); }
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
.fade-in-up {
|
| 68 |
-
animation: fadeInUp 0.4s ease-out forwards;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
.input-field {
|
| 72 |
-
transition: all 0.3s ease;
|
| 73 |
-
}
|
| 74 |
-
.input-field:focus {
|
| 75 |
-
border-color: #f59e0b;
|
| 76 |
-
box-shadow: 0 0 0 15px rgba(245, 158, 11, 0.15);
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
.step-section {
|
| 80 |
-
display: none;
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
.step-section.active {
|
| 84 |
-
display: block;
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
.step-indicator {
|
| 88 |
-
transition: all 0.3s ease;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
.step-indicator.active {
|
| 92 |
-
background: #f59e0b;
|
| 93 |
-
color: white;
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
.step-indicator.completed {
|
| 97 |
-
background: #22c55e;
|
| 98 |
-
color: white;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
.payment-option {
|
| 102 |
-
transition: all 0.3s ease;
|
| 103 |
-
cursor: pointer;
|
| 104 |
-
border: 2px solid #e5e7eb;
|
| 105 |
-
border-radius: 12px;
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
.payment-option:hover {
|
| 109 |
-
border-color: #f59e0b;
|
| 110 |
-
transform: translateY(-2px);
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
.payment-option.selected {
|
| 114 |
-
border-color: #f59e0b;
|
| 115 |
-
background: #fef2f2;
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
.place-order-btn {
|
| 119 |
-
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
| 120 |
-
transition: all 0.3s ease;
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
.place-order-btn:hover {
|
| 124 |
-
transform: translateY(-2px);
|
| 125 |
-
box-shadow: 0 15px 30px rgba(240, 71, 74, 0.15);
|
| 126 |
-
}
|
| 127 |
-
|
| 128 |
-
.place-order-btn:disabled {
|
| 129 |
-
opacity: 0.6;
|
| 130 |
-
cursor: not-allowed;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
/* Custom scrollbar */
|
| 134 |
-
::-webkit-scrollbar {
|
| 135 |
-
width: 6px;
|
| 136 |
-
}
|
| 137 |
-
|
| 138 |
-
::-webkit-scrollbar-track {
|
| 139 |
-
background: #f1f1f1;
|
| 140 |
-
border-radius: 10px;
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
::-webkit-scrollbar-thumb {
|
| 144 |
-
background: #c1c1c1;
|
| 145 |
-
border-radius: 10px;
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
::-webkit-scrollbar-thumb:hover {
|
| 149 |
-
background: #a1a1a1;
|
| 150 |
-
}
|
| 151 |
-
</style>
|
| 152 |
-
</head>
|
| 153 |
-
<body class="bg-gray-50 min-h-screen">
|
| 154 |
-
<!-- Loading Overlay -->
|
| 155 |
-
<div id="loading-overlay" class="fixed inset-0 bg-black/50 hidden z-50 flex items-center justify-center backdrop-blur-sm">
|
| 156 |
-
<div class="bg-white p-8 rounded-2xl shadow-xl">
|
| 157 |
-
<div class="flex flex-col items-center">
|
| 158 |
-
<div class="animate-spin rounded-full h-12 w-12 border-4 border-brand-500 border-t-transparent"></div>
|
| 159 |
-
<p class="mt-4 text-gray-700 font-medium" id="loading-text">Processing your order...</p>
|
| 160 |
-
</div>
|
| 161 |
-
</div>
|
| 162 |
-
</div>
|
| 163 |
-
|
| 164 |
-
<!-- Error Toast -->
|
| 165 |
-
<div id="error-toast" class="fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-xl shadow-lg hidden z-50 transition-all transform translate-x-full">
|
| 166 |
-
<div class="flex items-center gap-2">
|
| 167 |
-
<i class="fas fa-exclamation-circle"></i>
|
| 168 |
-
<span id="error-message">Error message</span>
|
| 169 |
-
</div>
|
| 170 |
-
</div>
|
| 171 |
-
|
| 172 |
-
<!-- Success Toast -->
|
| 173 |
-
<div id="success-toast" class="fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-xl shadow-lg hidden z-50 transition-all transform translate-x-full">
|
| 174 |
-
<div class="flex items-center gap-2">
|
| 175 |
-
<i class="fas fa-check-circle"></i>
|
| 176 |
-
<span id="success-message">Success message</span>
|
| 177 |
-
</div>
|
| 178 |
-
</div>
|
| 179 |
-
|
| 180 |
-
<!-- Header -->
|
| 181 |
-
<header class="bg-white shadow-sm sticky top-0 z-40">
|
| 182 |
-
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 183 |
-
<div class="flex justify-between items-center h-16">
|
| 184 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 185 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 186 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 187 |
-
<defs>
|
| 188 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 189 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 190 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 191 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 192 |
-
</linearGradient>
|
| 193 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 194 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 195 |
-
<feMerge>
|
| 196 |
-
<feMergeNode in="coloredBlur"/>
|
| 197 |
-
<feMergeNode in="SourceGraphic"/>
|
| 198 |
-
</feMerge>
|
| 199 |
-
</filter>
|
| 200 |
-
</defs>
|
| 201 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 202 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 203 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 204 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 205 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 206 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 207 |
-
<g transform="translate(52, 8)">
|
| 208 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 209 |
-
</g>
|
| 210 |
-
</svg>
|
| 211 |
-
</div>
|
| 212 |
-
<div class="flex flex-col">
|
| 213 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 214 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 215 |
-
</div>
|
| 216 |
-
</a>
|
| 217 |
-
<div class="flex items-center gap-4">
|
| 218 |
-
<a href="../index.html" class="text-gray-600 hover:text-brand-500 transition-colors">
|
| 219 |
-
<i class="fas fa-home text-xl"></i>
|
| 220 |
-
</a>
|
| 221 |
-
<div class="relative">
|
| 222 |
-
<i class="fas fa-shopping-cart text-xl text-gray-600"></i>
|
| 223 |
-
<span id="cart-count" class="absolute -top-2 -right-2 bg-brand-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 224 |
-
</div>
|
| 225 |
-
</div>
|
| 226 |
-
</div>
|
| 227 |
-
</nav>
|
| 228 |
-
</header>
|
| 229 |
-
|
| 230 |
-
<!-- Breadcrumb -->
|
| 231 |
-
<div class="bg-white border-b">
|
| 232 |
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
| 233 |
-
<nav class="flex items-center space-x-2 text-sm text-gray-500">
|
| 234 |
-
<a href="../index.html" class="hover:text-brand-500 transition-colors">Home</a>
|
| 235 |
-
<i class="fas fa-chevron-right text-xs"></i>
|
| 236 |
-
<a href="cart.html" class="hover:text-brand-500 transition-colors">Cart</a>
|
| 237 |
-
<i class="fas fa-chevron-right text-xs"></i>
|
| 238 |
-
<span class="text-gray-900 font-medium">Checkout</span>
|
| 239 |
-
</nav>
|
| 240 |
-
</div>
|
| 241 |
-
</div>
|
| 242 |
-
|
| 243 |
-
<!-- Progress Steps -->
|
| 244 |
-
<div class="bg-gradient-to-r from-primary-500 to-primary-600 py-3 sticky top-16 z-30">
|
| 245 |
-
<div class="max-w-7xl mx-auto px-4">
|
| 246 |
-
<div class="flex items-center justify-between">
|
| 247 |
-
<div class="flex items-center gap-3">
|
| 248 |
-
<div id="step-1-indicator" class="step-indicator active">
|
| 249 |
-
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-white text-brand-500 font-bold text-sm">1</span>
|
| 250 |
-
<span class="text-white text-sm">Shipping</span>
|
| 251 |
-
</div>
|
| 252 |
-
<div class="hidden sm:block h-1 w-px-4 border-l border-white/30"></div>
|
| 253 |
-
<div id="step-2-indicator" class="step-indicator">
|
| 254 |
-
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-white/70 text-brand-500 font-bold text-sm">2</span>
|
| 255 |
-
<span class="text-white/70 text-sm">Payment</span>
|
| 256 |
-
</div>
|
| 257 |
-
<div class="hidden sm:block h-1 w-px-4 border-l border-white/30"></div>
|
| 258 |
-
<div id="step-3-indicator" class="step-indicator">
|
| 259 |
-
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-white/70 text-brand-500 font-bold text-sm">3</span>
|
| 260 |
-
<span class="text-white/70 text-sm">Review</span>
|
| 261 |
-
</div>
|
| 262 |
-
<a href="cart.html" class="text-white text-sm hover:text-white/80 transition-colors">
|
| 263 |
-
<i class="fas fa-arrow-left mr-2"></i>
|
| 264 |
-
Back to Cart
|
| 265 |
-
</a>
|
| 266 |
-
</div>
|
| 267 |
-
</div>
|
| 268 |
-
</div>
|
| 269 |
-
|
| 270 |
-
<!-- Main Content -->
|
| 271 |
-
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
| 272 |
-
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 273 |
-
<!-- Left Column - Steps -->
|
| 274 |
-
<div class="lg:col-span-2 space-y-6">
|
| 275 |
-
<!-- Step 1: Shipping Information -->
|
| 276 |
-
<section id="step-1" class="step-section active bg-white rounded-2xl shadow-lg p-6">
|
| 277 |
-
<div class="flex items-center gap-3 mb-6">
|
| 278 |
-
<div class="w-10 h-10 bg-brand-500-100 rounded-lg flex items-center justify-center">
|
| 279 |
-
<i class="fas fa-truck text-brand-500 text-lg"></i>
|
| 280 |
-
</div>
|
| 281 |
-
<h2 class="text-xl font-bold text-gray-900">Shipping Information</h2>
|
| 282 |
-
</div>
|
| 283 |
-
|
| 284 |
-
<form id="shipping-form" class="space-y-4">
|
| 285 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 286 |
-
<div>
|
| 287 |
-
<label for="fullname" class="block text-sm font-medium text-gray-700 mb-1">Full Name *</label>
|
| 288 |
-
<input type="text" id="fullname" required
|
| 289 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-primary/20">
|
| 290 |
-
</div>
|
| 291 |
-
<div>
|
| 292 |
-
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email Address *</label>
|
| 293 |
-
<input type="email" id="email" required
|
| 294 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-primary/20">
|
| 295 |
-
</div>
|
| 296 |
-
</div>
|
| 297 |
-
<div>
|
| 298 |
-
<label for="phone" class="block text-sm font-medium text-gray-700 mb-1">Phone Number *</label>
|
| 299 |
-
<input type="tel" id="phone" required
|
| 300 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-primary/20">
|
| 301 |
-
</div>
|
| 302 |
-
<div>
|
| 303 |
-
<label for="address" class="block text-sm font-medium text-gray-700 mb-1">Street Address *</label>
|
| 304 |
-
<input type="text" id="address" required
|
| 305 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-primary/20">
|
| 306 |
-
</div>
|
| 307 |
-
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
| 308 |
-
<div>
|
| 309 |
-
<label for="city" class="block text-sm font-medium text-gray-700 mb-1">City *</label>
|
| 310 |
-
<input type="text" id="city" required
|
| 311 |
-
class="input-field px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-primary/20">
|
| 312 |
-
</div>
|
| 313 |
-
<div>
|
| 314 |
-
<label for="state" class="block text-sm font-medium text-gray-700 mb-1">State *</label>
|
| 315 |
-
<input type="text" id="state" required
|
| 316 |
-
class="input-field px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-primary/20">
|
| 317 |
-
</div>
|
| 318 |
-
<div>
|
| 319 |
-
<label for="zipcode" class="block text-sm font-medium text-gray-700 mb-1">ZIP Code *</label>
|
| 320 |
-
<input type="text" id="zipcode" required
|
| 321 |
-
class="input-field px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-primary/20">
|
| 322 |
-
</div>
|
| 323 |
-
</div>
|
| 324 |
-
</form>
|
| 325 |
-
</section>
|
| 326 |
-
|
| 327 |
-
<!-- Step 2: Payment Method -->
|
| 328 |
-
<section id="step-2" class="step-section bg-white rounded-2xl shadow-lg p-6">
|
| 329 |
-
<div class="flex items-center gap-3 mb-6">
|
| 330 |
-
<div class="w-10 h-10 bg-brand-500-100 rounded-lg flex items-center justify-center">
|
| 331 |
-
<i class="fas fa-credit-card text-brand-500 text-lg"></i>
|
| 332 |
-
</div>
|
| 333 |
-
<h2 class="text-xl font-bold text-gray-900">Payment Method</h2>
|
| 334 |
-
</div>
|
| 335 |
-
|
| 336 |
-
<!-- EeshaPay Option -->
|
| 337 |
-
<div class="mb-4">
|
| 338 |
-
<label class="payment-option block p-4 border-2 border-gray-200 rounded-xl cursor-pointer" onclick="selectPayment('eeshapay')">
|
| 339 |
-
<div class="flex items-center justify-between">
|
| 340 |
-
<div class="flex items-center gap-3">
|
| 341 |
-
<div class="w-12 h-12 bg-gradient-to-br from-gray-800 to-gray-900 rounded-lg flex items-center justify-center">
|
| 342 |
-
<span class="text-white font-bold text-xs">EeshaPay</span>
|
| 343 |
-
</div>
|
| 344 |
-
<div>
|
| 345 |
-
<p class="font-semibold text-gray-900">EeshaPay Wallet</p>
|
| 346 |
-
<p class="text-sm text-gray-500">Pay with your wallet balance</p>
|
| 347 |
-
<p class="text-xs text-green-600 mt-1" id="wallet-balance">Balance: Loading...</p>
|
| 348 |
-
</div>
|
| 349 |
-
</div>
|
| 350 |
-
<div class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center" id="eeshapay-check">
|
| 351 |
-
<i class="fas fa-check text-brand-500 hidden"></i>
|
| 352 |
-
</div>
|
| 353 |
-
</div>
|
| 354 |
-
</label>
|
| 355 |
-
</div>
|
| 356 |
-
|
| 357 |
-
<!-- Credit Card Option -->
|
| 358 |
-
<div class="mb-4">
|
| 359 |
-
<label class="payment-option block p-4 border-2 border-gray-200 rounded-xl cursor-pointer" onclick="selectPayment('card')">
|
| 360 |
-
<div class="flex items-center justify-between">
|
| 361 |
-
<div class="flex items-center gap-3">
|
| 362 |
-
<div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-700 rounded-lg flex items-center justify-center">
|
| 363 |
-
<i class="fas fa-credit-card text-white text-lg"></i>
|
| 364 |
-
</div>
|
| 365 |
-
<div>
|
| 366 |
-
<p class="font-semibold text-gray-900">Credit / Debit Card</p>
|
| 367 |
-
<p class="text-sm text-gray-500">Visa, Mastercard, Amex</p>
|
| 368 |
-
</div>
|
| 369 |
-
</div>
|
| 370 |
-
<div class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center" id="card-check">
|
| 371 |
-
<i class="fas fa-check text-brand-500 hidden"></i>
|
| 372 |
-
</div>
|
| 373 |
-
</div>
|
| 374 |
-
</label>
|
| 375 |
-
</div>
|
| 376 |
-
|
| 377 |
-
<!-- Cash on Delivery Option -->
|
| 378 |
-
<div class="mb-4">
|
| 379 |
-
<label class="payment-option block p-4 border-2 border-gray-200 rounded-xl cursor-pointer" onclick="selectPayment('cod')">
|
| 380 |
-
<div class="flex items-center justify-between">
|
| 381 |
-
<div class="flex items-center gap-3">
|
| 382 |
-
<div class="w-12 h-12 bg-gradient-to-br from-green-500 to-green-700 rounded-lg flex items-center justify-center">
|
| 383 |
-
<i class="fas fa-money-bill-wave text-white text-lg"></i>
|
| 384 |
-
</div>
|
| 385 |
-
<div>
|
| 386 |
-
<p class="font-semibold text-gray-900">Cash on Delivery</p>
|
| 387 |
-
<p class="text-sm text-gray-500">Pay when you receive your order</p>
|
| 388 |
-
<p class="text-xs text-orange-600 mt-1">+$5.00 COD fee</p>
|
| 389 |
-
</div>
|
| 390 |
-
</div>
|
| 391 |
-
<div class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center" id="cod-check">
|
| 392 |
-
<i class="fas fa-check text-brand-500 hidden"></i>
|
| 393 |
-
</div>
|
| 394 |
-
</div>
|
| 395 |
-
</label>
|
| 396 |
-
</div>
|
| 397 |
-
|
| 398 |
-
<!-- Card Details Form -->
|
| 399 |
-
<div id="card-form" class="hidden mt-4 p-4 bg-gray-50 rounded-xl">
|
| 400 |
-
<h3 class="font-semibold text-gray-900 mb-3">Card Details</h3>
|
| 401 |
-
<div class="space-y-4">
|
| 402 |
-
<div>
|
| 403 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Card Number</label>
|
| 404 |
-
<input type="text" id="card-number" placeholder="1234 5678 9012 3456" maxlength="19"
|
| 405 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 406 |
-
</div>
|
| 407 |
-
<div class="grid grid-cols-2 gap-4">
|
| 408 |
-
<div>
|
| 409 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Expiry Date</label>
|
| 410 |
-
<input type="text" id="card-expiry" placeholder="MM/YY" maxlength="7"
|
| 411 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 412 |
-
</div>
|
| 413 |
-
<div>
|
| 414 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">CVV</label>
|
| 415 |
-
<input type="text" id="card-cvv" placeholder="123" maxlength="4"
|
| 416 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 417 |
-
</div>
|
| 418 |
-
</div>
|
| 419 |
-
<div>
|
| 420 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Name on Card</label>
|
| 421 |
-
<input type="text" id="card-name" placeholder="John Doe"
|
| 422 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 423 |
-
</div>
|
| 424 |
-
</div>
|
| 425 |
-
</div>
|
| 426 |
-
</section>
|
| 427 |
-
|
| 428 |
-
<!-- Step 3: Review & Place Order -->
|
| 429 |
-
<section id="step-3" class="step-section bg-white rounded-2xl shadow-lg p-6">
|
| 430 |
-
<div class="flex items-center gap-3 mb-6">
|
| 431 |
-
<div class="w-10 h-10 bg-brand-500-100 rounded-lg flex items-center justify-center">
|
| 432 |
-
<i class="fas fa-check-circle text-brand-500 text-lg"></i>
|
| 433 |
-
</div>
|
| 434 |
-
<h2 class="text-xl font-bold text-gray-900">Review & Place Order</h2>
|
| 435 |
-
</div>
|
| 436 |
-
|
| 437 |
-
<!-- Order Summary Review -->
|
| 438 |
-
<div id="review-summary" class="space-y-4">
|
| 439 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 440 |
-
<h4 class="text-sm font-semibold text-gray-700 mb-2">Shipping Address</h4>
|
| 441 |
-
<p class="text-gray-600 text-sm" id="review-shipping"></p>
|
| 442 |
-
</div>
|
| 443 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 444 |
-
<h4 class="text-sm font-semibold text-gray-700 mb-2">Payment Method</h4>
|
| 445 |
-
<p class="text-gray-600 text-sm" id="review-payment"></p>
|
| 446 |
-
</div>
|
| 447 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 448 |
-
<h4 class="text-sm font-semibold text-gray-700 mb-2">Order Items</h4>
|
| 449 |
-
<div id="review-items" class="text-gray-600 text-sm"></div>
|
| 450 |
-
</div>
|
| 451 |
-
</div>
|
| 452 |
-
|
| 453 |
-
<!-- Place Order Button -->
|
| 454 |
-
<button onclick="placeOrder()" id="place-order-btn"
|
| 455 |
-
class="place-order-btn w-full py-4 text-white font-semibold rounded-xl flex items-center justify-center gap-2">
|
| 456 |
-
<i class="fas fa-lock"></i>
|
| 457 |
-
<span id="place-order-text">Place Order</span>
|
| 458 |
-
</button>
|
| 459 |
-
|
| 460 |
-
<p class="text-center text-gray-500 text-sm mt-4">
|
| 461 |
-
<i class="fas fa-shield-alt mr-1"></i>
|
| 462 |
-
Secure checkout with SSL encryption
|
| 463 |
-
</p>
|
| 464 |
-
</section>
|
| 465 |
-
</div>
|
| 466 |
-
|
| 467 |
-
<!-- Right Column - Order Summary -->
|
| 468 |
-
<div class="lg:col-span-1">
|
| 469 |
-
<div class="bg-white rounded-2xl shadow-lg p-6 sticky top-24">
|
| 470 |
-
<h3 class="text-lg font-bold text-gray-900 mb-4">Order Summary</h3>
|
| 471 |
-
<div id="order-items" class="space-y-4 max-h-64 overflow-y-auto mb-4">
|
| 472 |
-
<!-- Items populated by JS -->
|
| 473 |
-
</div>
|
| 474 |
-
|
| 475 |
-
<div class="border-t pt-4 mt-4 space-y-3">
|
| 476 |
-
<div class="flex justify-between text-gray-600">
|
| 477 |
-
<span>Subtotal</span>
|
| 478 |
-
<span id="subtotal" class="font-semibold text-gray-900">$0.00</span>
|
| 479 |
-
</div>
|
| 480 |
-
<div class="flex justify-between text-gray-600">
|
| 481 |
-
<span>Shipping</span>
|
| 482 |
-
<span id="shipping-cost" class="font-semibold text-gray-900">$0.00</span>
|
| 483 |
-
</div>
|
| 484 |
-
<div class="flex justify-between text-gray-600">
|
| 485 |
-
<span>Tax (10%)</span>
|
| 486 |
-
<span id="tax" class="font-semibold text-gray-900">$0.00</span>
|
| 487 |
-
</div>
|
| 488 |
-
<div class="flex justify-between text-gray-600 pt-4 border-t border-dashed">
|
| 489 |
-
<span>COD Fee</span>
|
| 490 |
-
<span id="cod-fee" class="font-semibold text-orange-600">$0.00</span>
|
| 491 |
-
</div>
|
| 492 |
-
<div class="flex justify-between items-center pt-4 border-t-2 border-gray-900">
|
| 493 |
-
<span class="text-lg font-bold text-gray-900">Total</span>
|
| 494 |
-
<span id="total" class="text-2xl font-bold text-brand-500">$0.00</span>
|
| 495 |
-
</div>
|
| 496 |
-
</div>
|
| 497 |
-
</div>
|
| 498 |
-
</div>
|
| 499 |
-
</div>
|
| 500 |
-
|
| 501 |
-
<!-- Fixed Navigation -->
|
| 502 |
-
<div class="fixed bottom-6 left-6 md:left-auto md:right-6 lg:right-12 z-50 bg-white/95 backdrop-blur-sm shadow-lg rounded-full px-6 py-4 max-w-3xl mx-auto flex items-center justify-between gap-4">
|
| 503 |
-
<button id="back-btn" onclick="goBack()" class="px-6 py-3 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition-colors">
|
| 504 |
-
<i class="fas fa-arrow-left mr-2"></i>
|
| 505 |
-
Back
|
| 506 |
-
</button>
|
| 507 |
-
<div class="flex items-center gap-4">
|
| 508 |
-
<span class="text-sm text-gray-500">Step <span id="step-label">1</span> of 3</span>
|
| 509 |
-
</div>
|
| 510 |
-
<button id="continue-btn" onclick="goNext()" class="px-6 py-3 bg-brand-500 text-white rounded-lg hover:bg-brand-500-600 transition-colors">
|
| 511 |
-
<span id="continue-text">Continue</span>
|
| 512 |
-
<i class="fas fa-arrow-right ml-2"></i>
|
| 513 |
-
</button>
|
| 514 |
-
</div>
|
| 515 |
-
</div>
|
| 516 |
-
|
| 517 |
-
<script>
|
| 518 |
-
// State
|
| 519 |
-
let currentStep = 1;
|
| 520 |
-
let pendingOrder = null;
|
| 521 |
-
let selectedPayment = 'eeshapay';
|
| 522 |
-
let currentUser = null;
|
| 523 |
-
let walletBalance = 0;
|
| 524 |
-
|
| 525 |
-
// Initialize
|
| 526 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 527 |
-
await checkAuth();
|
| 528 |
-
if (currentUser) {
|
| 529 |
-
await loadCheckoutData();
|
| 530 |
-
}
|
| 531 |
-
});
|
| 532 |
-
|
| 533 |
-
// Check authentication
|
| 534 |
-
async function checkAuth() {
|
| 535 |
-
const { success, user } = await window.EdgeFunctions.auth.checkSession();
|
| 536 |
-
currentUser = user || null;
|
| 537 |
-
|
| 538 |
-
if (!currentUser) {
|
| 539 |
-
showToast('Please login to continue checkout', 'error');
|
| 540 |
-
setTimeout(() => {
|
| 541 |
-
window.location.href = 'login.html?redirect=checkout';
|
| 542 |
-
}, 1500);
|
| 543 |
-
}
|
| 544 |
-
}
|
| 545 |
-
|
| 546 |
-
// Load checkout data (cart items, wallet balance, total) via edge function.
|
| 547 |
-
// The edge function returns everything prepare() needs atomically —
|
| 548 |
-
// no Supabase calls, no localStorage, no pending_orders table.
|
| 549 |
-
async function loadCheckoutData() {
|
| 550 |
-
try {
|
| 551 |
-
const result = await window.EdgeFunctions.checkout.prepare();
|
| 552 |
-
|
| 553 |
-
if (!result.success) {
|
| 554 |
-
showToast(result.error || 'Failed to load checkout. Please try again.', 'error');
|
| 555 |
-
return;
|
| 556 |
-
}
|
| 557 |
-
|
| 558 |
-
const cartItems = result.cartItems || [];
|
| 559 |
-
walletBalance = result.walletBalance || 0;
|
| 560 |
-
|
| 561 |
-
// Empty cart → redirect to cart page
|
| 562 |
-
if (!cartItems.length) {
|
| 563 |
-
showToast('Your cart is empty. Redirecting to cart...', 'error');
|
| 564 |
-
setTimeout(() => window.location.href = 'cart.html', 1500);
|
| 565 |
-
return;
|
| 566 |
-
}
|
| 567 |
-
|
| 568 |
-
// Normalize cart items into the shape renderOrderSummary() expects.
|
| 569 |
-
const items = cartItems.map(item => ({
|
| 570 |
-
image: item.image || item.products?.image_url || item.products?.image || '',
|
| 571 |
-
name: item.name || item.products?.name || 'Product',
|
| 572 |
-
quantity: item.quantity || 1,
|
| 573 |
-
price: (item.price != null ? item.price : (item.products?.price || 0)),
|
| 574 |
-
}));
|
| 575 |
-
|
| 576 |
-
const subtotal = items.reduce((s, it) => s + it.price * it.quantity, 0);
|
| 577 |
-
const total = (result.total != null ? result.total : subtotal);
|
| 578 |
-
|
| 579 |
-
pendingOrder = {
|
| 580 |
-
items,
|
| 581 |
-
subtotal,
|
| 582 |
-
shipping: 0,
|
| 583 |
-
tax: 0,
|
| 584 |
-
total,
|
| 585 |
-
};
|
| 586 |
-
|
| 587 |
-
// Update wallet balance display
|
| 588 |
-
document.getElementById('wallet-balance').textContent = `Balance: $${walletBalance.toFixed(2)}`;
|
| 589 |
-
|
| 590 |
-
renderOrderSummary();
|
| 591 |
-
} catch (error) {
|
| 592 |
-
console.error('Error loading checkout data:', error);
|
| 593 |
-
showToast('Error loading checkout. Please try again.', 'error');
|
| 594 |
-
}
|
| 595 |
-
}
|
| 596 |
-
|
| 597 |
-
// Render order summary in sidebar
|
| 598 |
-
function renderOrderSummary() {
|
| 599 |
-
if (!pendingOrder) return;
|
| 600 |
-
|
| 601 |
-
const container = document.getElementById('order-items');
|
| 602 |
-
container.innerHTML = pendingOrder.items.map(item => `
|
| 603 |
-
<div class="flex items-center gap-3 py-2">
|
| 604 |
-
<div class="w-12 h-12 bg-gray-100 rounded-lg overflow-hidden">
|
| 605 |
-
<img src="${item.image || 'https://via.placeholder.com/48'}" alt="${item.name}" class="w-full h-full object-cover">
|
| 606 |
-
</div>
|
| 607 |
-
<div class="flex-1 min-w-0">
|
| 608 |
-
<p class="text-sm font-medium text-gray-900 truncate">${item.name}</p>
|
| 609 |
-
<p class="text-xs text-gray-500">Qty: ${item.quantity}</p>
|
| 610 |
-
</div>
|
| 611 |
-
<div class="text-right">
|
| 612 |
-
<p class="text-sm font-semibold text-gray-900">$${(item.price * item.quantity).toFixed(2)}</p>
|
| 613 |
-
</div>
|
| 614 |
-
</div>
|
| 615 |
-
`).join('');
|
| 616 |
-
|
| 617 |
-
// Update totals
|
| 618 |
-
document.getElementById('subtotal').textContent = `$${(pendingOrder.subtotal || 0).toFixed(2)}`;
|
| 619 |
-
document.getElementById('shipping-cost').textContent = `$${(pendingOrder.shipping || 0).toFixed(2)}`;
|
| 620 |
-
document.getElementById('tax').textContent = `$${(pendingOrder.tax || 0).toFixed(2)}`;
|
| 621 |
-
|
| 622 |
-
const codFee = selectedPayment === 'cod' ? 5 : 0;
|
| 623 |
-
document.getElementById('cod-fee').textContent = `$${codFee.toFixed(2)}`;
|
| 624 |
-
document.getElementById('cod-fee').parentElement.classList.toggle('hidden', selectedPayment !== 'cod');
|
| 625 |
-
|
| 626 |
-
const total = (pendingOrder.total || 0) + codFee;
|
| 627 |
-
document.getElementById('total').textContent = `$${total.toFixed(2)}`;
|
| 628 |
-
}
|
| 629 |
-
|
| 630 |
-
// Step navigation
|
| 631 |
-
function showStep(step) {
|
| 632 |
-
currentStep = step;
|
| 633 |
-
|
| 634 |
-
// Hide all steps
|
| 635 |
-
document.querySelectorAll('.step-section').forEach(s => s.classList.remove('active'));
|
| 636 |
-
|
| 637 |
-
// Show current step
|
| 638 |
-
document.getElementById(`step-${step}`).classList.add('active');
|
| 639 |
-
|
| 640 |
-
// Update indicators
|
| 641 |
-
document.querySelectorAll('.step-indicator').forEach((indicator, index) => {
|
| 642 |
-
indicator.classList.remove('active', 'completed');
|
| 643 |
-
if (index + 1 < step) {
|
| 644 |
-
indicator.classList.add('completed');
|
| 645 |
-
} else if (index + 1 === step) {
|
| 646 |
-
indicator.classList.add('active');
|
| 647 |
-
}
|
| 648 |
-
});
|
| 649 |
-
|
| 650 |
-
// Update buttons
|
| 651 |
-
const backBtn = document.getElementById('back-btn');
|
| 652 |
-
const continueBtn = document.getElementById('continue-btn');
|
| 653 |
-
const continueText = document.getElementById('continue-text');
|
| 654 |
-
|
| 655 |
-
backBtn.classList.toggle('hidden', step === 1);
|
| 656 |
-
|
| 657 |
-
if (step === 3) {
|
| 658 |
-
continueBtn.classList.add('hidden');
|
| 659 |
-
document.getElementById('place-order-btn').classList.remove('hidden');
|
| 660 |
-
} else {
|
| 661 |
-
continueBtn.classList.remove('hidden');
|
| 662 |
-
document.getElementById('place-order-btn').classList.add('hidden');
|
| 663 |
-
continueText.textContent = step === 1 ? 'Continue to Payment' : 'Review Order';
|
| 664 |
-
}
|
| 665 |
-
|
| 666 |
-
// Update step label
|
| 667 |
-
document.getElementById('step-label').textContent = step;
|
| 668 |
-
}
|
| 669 |
-
|
| 670 |
-
function goNext() {
|
| 671 |
-
if (currentStep === 1) {
|
| 672 |
-
// Validate shipping form
|
| 673 |
-
if (!validateShippingForm()) return;
|
| 674 |
-
showStep(2);
|
| 675 |
-
} else if (currentStep === 2) {
|
| 676 |
-
showStep(3);
|
| 677 |
-
updateReview();
|
| 678 |
-
}
|
| 679 |
-
}
|
| 680 |
-
|
| 681 |
-
function goBack() {
|
| 682 |
-
if (currentStep > 1) {
|
| 683 |
-
showStep(currentStep - 1);
|
| 684 |
-
}
|
| 685 |
-
}
|
| 686 |
-
|
| 687 |
-
// Validate shipping form
|
| 688 |
-
function validateShippingForm() {
|
| 689 |
-
const fields = ['fullname', 'email', 'phone', 'address', 'city', 'state', 'zipcode'];
|
| 690 |
-
for (const field of fields) {
|
| 691 |
-
const input = document.getElementById(field);
|
| 692 |
-
if (!input.value.trim()) {
|
| 693 |
-
showToast(`Please fill in ${field.replace('_', ' ')}`, 'error');
|
| 694 |
-
input.focus();
|
| 695 |
-
return false;
|
| 696 |
-
}
|
| 697 |
-
}
|
| 698 |
-
|
| 699 |
-
// Validate email format
|
| 700 |
-
const email = document.getElementById('email').value;
|
| 701 |
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
| 702 |
-
showToast('Please enter a valid email address', 'error');
|
| 703 |
-
return false;
|
| 704 |
-
}
|
| 705 |
-
|
| 706 |
-
return true;
|
| 707 |
-
}
|
| 708 |
-
|
| 709 |
-
// Select payment method
|
| 710 |
-
function selectPayment(method) {
|
| 711 |
-
selectedPayment = method;
|
| 712 |
-
|
| 713 |
-
// Update UI
|
| 714 |
-
document.querySelectorAll('.payment-option').forEach(opt => {
|
| 715 |
-
opt.classList.remove('selected');
|
| 716 |
-
opt.querySelector('.fa-check').classList.add('hidden');
|
| 717 |
-
});
|
| 718 |
-
|
| 719 |
-
const selected = document.querySelector(`[onclick="selectPayment('${method}')"]`);
|
| 720 |
-
selected.classList.add('selected');
|
| 721 |
-
selected.querySelector('.fa-check').classList.remove('hidden');
|
| 722 |
-
|
| 723 |
-
// Show/hide card form
|
| 724 |
-
document.getElementById('card-form').classList.toggle('hidden', method !== 'card');
|
| 725 |
-
|
| 726 |
-
// Update totals if COD
|
| 727 |
-
renderOrderSummary();
|
| 728 |
-
}
|
| 729 |
-
|
| 730 |
-
// Update review section
|
| 731 |
-
function updateReview() {
|
| 732 |
-
// Shipping address
|
| 733 |
-
const shipping = `
|
| 734 |
-
<p>${document.getElementById('fullname').value}</p>
|
| 735 |
-
<p>${document.getElementById('address').value}</p>
|
| 736 |
-
<p>${document.getElementById('city').value}, ${document.getElementById('state').value} ${document.getElementById('zipcode').value}</p>
|
| 737 |
-
`;
|
| 738 |
-
document.getElementById('review-shipping').innerHTML = shipping;
|
| 739 |
-
|
| 740 |
-
// Payment method
|
| 741 |
-
let paymentText = '';
|
| 742 |
-
if (selectedPayment === 'eeshapay') {
|
| 743 |
-
paymentText = `<p class="font-semibold text-brand-500">EeshaPay Wallet</p><p class="text-sm text-gray-500">Balance: $${walletBalance.toFixed(2)}</p>`;
|
| 744 |
-
} else if (selectedPayment === 'card') {
|
| 745 |
-
const last4 = document.getElementById('card-number').value.slice(-4);
|
| 746 |
-
paymentText = `<p class="font-semibold">Card ending in ${last4}</p><p class="text-sm text-gray-500">${document.getElementById('card-name').value}</p>`;
|
| 747 |
-
} else {
|
| 748 |
-
paymentText = `<p class="font-semibold text-green-600">Cash on Delivery</p><p class="text-sm text-gray-500">Pay when you receive your order (+$5.00 fee)</p>`;
|
| 749 |
-
}
|
| 750 |
-
document.getElementById('review-payment').innerHTML = paymentText;
|
| 751 |
-
|
| 752 |
-
// Order items
|
| 753 |
-
const itemsHtml = pendingOrder.items.map(item => `
|
| 754 |
-
<div class="flex justify-between py-1">
|
| 755 |
-
<span>${item.name} x ${item.quantity}</span>
|
| 756 |
-
<span>$${(item.price * item.quantity).toFixed(2)}</span>
|
| 757 |
-
</div>
|
| 758 |
-
`).join('');
|
| 759 |
-
document.getElementById('review-items').innerHTML = itemsHtml;
|
| 760 |
-
}
|
| 761 |
-
|
| 762 |
-
// Place order
|
| 763 |
-
async function placeOrder() {
|
| 764 |
-
// Validate card details if card payment
|
| 765 |
-
if (selectedPayment === 'card') {
|
| 766 |
-
const cardNumber = document.getElementById('card-number').value;
|
| 767 |
-
const cardExpiry = document.getElementById('card-expiry').value;
|
| 768 |
-
const cardCvv = document.getElementById('card-cvv').value;
|
| 769 |
-
const cardName = document.getElementById('card-name').value;
|
| 770 |
-
|
| 771 |
-
if (!cardNumber || cardNumber.length < 16) {
|
| 772 |
-
showToast('Please enter a valid card number', 'error');
|
| 773 |
-
return;
|
| 774 |
-
}
|
| 775 |
-
if (!cardExpiry || !cardCvv || !cardName) {
|
| 776 |
-
showToast('Please fill in all card details', 'error');
|
| 777 |
-
return;
|
| 778 |
-
}
|
| 779 |
-
}
|
| 780 |
-
|
| 781 |
-
// Check EeshaPay balance
|
| 782 |
-
if (selectedPayment === 'eeshapay') {
|
| 783 |
-
if (walletBalance < pendingOrder.total) {
|
| 784 |
-
showToast('Insufficient wallet balance. Please add funds or choose another payment method.', 'error');
|
| 785 |
-
return;
|
| 786 |
-
}
|
| 787 |
-
}
|
| 788 |
-
|
| 789 |
-
// Show loading
|
| 790 |
-
showLoading(true);
|
| 791 |
-
|
| 792 |
-
try {
|
| 793 |
-
const shippingAddress = {
|
| 794 |
-
fullname: document.getElementById('fullname').value,
|
| 795 |
-
email: document.getElementById('email').value,
|
| 796 |
-
phone: document.getElementById('phone').value,
|
| 797 |
-
address: document.getElementById('address').value,
|
| 798 |
-
city: document.getElementById('city').value,
|
| 799 |
-
state: document.getElementById('state').value,
|
| 800 |
-
zipcode: document.getElementById('zipcode').value
|
| 801 |
-
};
|
| 802 |
-
|
| 803 |
-
// The edge function handles ALL atomic operations server-side:
|
| 804 |
-
// debit wallet, create order, create transaction, clear cart.
|
| 805 |
-
// The frontend just calls placeOrder() and reads the result.
|
| 806 |
-
const result = await window.EdgeFunctions.checkout.placeOrder(shippingAddress);
|
| 807 |
-
|
| 808 |
-
if (!result.success) {
|
| 809 |
-
throw new Error(result.error || 'Failed to place order');
|
| 810 |
-
}
|
| 811 |
-
|
| 812 |
-
// Update local wallet balance from server response
|
| 813 |
-
if (typeof result.newBalance === 'number') {
|
| 814 |
-
walletBalance = result.newBalance;
|
| 815 |
-
}
|
| 816 |
-
|
| 817 |
-
// Show success
|
| 818 |
-
showLoading(false);
|
| 819 |
-
showToast('Order placed successfully!', 'success');
|
| 820 |
-
|
| 821 |
-
// Redirect to confirmation
|
| 822 |
-
setTimeout(() => {
|
| 823 |
-
window.location.href = `order-confirmation.html?orderId=${result.orderId}`;
|
| 824 |
-
}, 1000);
|
| 825 |
-
|
| 826 |
-
} catch (error) {
|
| 827 |
-
console.error('Error placing order:', error);
|
| 828 |
-
showLoading(false);
|
| 829 |
-
showToast(error.message || 'Error placing order. Please try again.', 'error');
|
| 830 |
-
}
|
| 831 |
-
}
|
| 832 |
-
|
| 833 |
-
// UI Helpers
|
| 834 |
-
function showLoading(show) {
|
| 835 |
-
document.getElementById('loading-overlay').classList.toggle('hidden', !show);
|
| 836 |
-
}
|
| 837 |
-
|
| 838 |
-
function showToast(message, type) {
|
| 839 |
-
const toast = document.getElementById(`${type}-toast`);
|
| 840 |
-
document.getElementById(`${type}-message`).textContent = message;
|
| 841 |
-
toast.classList.remove('hidden');
|
| 842 |
-
toast.classList.remove('translate-x-full');
|
| 843 |
-
|
| 844 |
-
setTimeout(() => {
|
| 845 |
-
toast.classList.add('translate-x-full');
|
| 846 |
-
setTimeout(() => toast.classList.add('hidden'), 300);
|
| 847 |
-
}, 3000);
|
| 848 |
-
}
|
| 849 |
-
|
| 850 |
-
// Format card number input
|
| 851 |
-
document.getElementById('card-number')?.addEventListener('input', function(e) {
|
| 852 |
-
let value = e.target.value.replace(/\s/g, '').replace(/\D/g, '');
|
| 853 |
-
value = value.replace(/(\d{4})/g, '$1 ').trim();
|
| 854 |
-
e.target.value = value;
|
| 855 |
-
});
|
| 856 |
-
</script>
|
| 857 |
-
</body>
|
| 858 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/css/index.css
DELETED
|
@@ -1,72 +0,0 @@
|
|
| 1 |
-
/* latin-ext */
|
| 2 |
-
@font-face {
|
| 3 |
-
font-family: 'Caladea';
|
| 4 |
-
font-style: italic;
|
| 5 |
-
font-weight: 400;
|
| 6 |
-
font-display: swap;
|
| 7 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJExBugZ7AAjhybUvR1N9vo3huR_DaS8.woff2) format('woff2');
|
| 8 |
-
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
| 9 |
-
}
|
| 10 |
-
/* latin */
|
| 11 |
-
@font-face {
|
| 12 |
-
font-family: 'Caladea';
|
| 13 |
-
font-style: italic;
|
| 14 |
-
font-weight: 400;
|
| 15 |
-
font-display: swap;
|
| 16 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJExBugZ7AAjhybUvR1N-Po3huR_DQ.woff2) format('woff2');
|
| 17 |
-
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
| 18 |
-
}
|
| 19 |
-
/* latin-ext */
|
| 20 |
-
@font-face {
|
| 21 |
-
font-family: 'Caladea';
|
| 22 |
-
font-style: italic;
|
| 23 |
-
font-weight: 700;
|
| 24 |
-
font-display: swap;
|
| 25 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE0BugZ7AAjhybUvR1FQ98ipcZyJ561DbZg.woff2) format('woff2');
|
| 26 |
-
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
| 27 |
-
}
|
| 28 |
-
/* latin */
|
| 29 |
-
@font-face {
|
| 30 |
-
font-family: 'Caladea';
|
| 31 |
-
font-style: italic;
|
| 32 |
-
font-weight: 700;
|
| 33 |
-
font-display: swap;
|
| 34 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE0BugZ7AAjhybUvR1FQ98iq8ZyJ561DQ.woff2) format('woff2');
|
| 35 |
-
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
| 36 |
-
}
|
| 37 |
-
/* latin-ext */
|
| 38 |
-
@font-face {
|
| 39 |
-
font-family: 'Caladea';
|
| 40 |
-
font-style: normal;
|
| 41 |
-
font-weight: 400;
|
| 42 |
-
font-display: swap;
|
| 43 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJEzBugZ7AAjhybUvRZ9-vAwpOZPDA.woff2) format('woff2');
|
| 44 |
-
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
| 45 |
-
}
|
| 46 |
-
/* latin */
|
| 47 |
-
@font-face {
|
| 48 |
-
font-family: 'Caladea';
|
| 49 |
-
font-style: normal;
|
| 50 |
-
font-weight: 400;
|
| 51 |
-
font-display: swap;
|
| 52 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJEzBugZ7AAjhybUvRh9-vAwpOY.woff2) format('woff2');
|
| 53 |
-
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
| 54 |
-
}
|
| 55 |
-
/* latin-ext */
|
| 56 |
-
@font-face {
|
| 57 |
-
font-family: 'Caladea';
|
| 58 |
-
font-style: normal;
|
| 59 |
-
font-weight: 700;
|
| 60 |
-
font-display: swap;
|
| 61 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE2BugZ7AAjhybUtaNY79MSqcx1BZyFDA.woff2) format('woff2');
|
| 62 |
-
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
| 63 |
-
}
|
| 64 |
-
/* latin */
|
| 65 |
-
@font-face {
|
| 66 |
-
font-family: 'Caladea';
|
| 67 |
-
font-style: normal;
|
| 68 |
-
font-weight: 700;
|
| 69 |
-
font-display: swap;
|
| 70 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE2BugZ7AAjhybUtaNY790Sqcx1BZw.woff2) format('woff2');
|
| 71 |
-
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
| 72 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/css/loader.css
DELETED
|
File without changes
|
Eesha buying folder/css/product.css
DELETED
|
File without changes
|
Eesha buying folder/css/style.css
DELETED
|
@@ -1,203 +0,0 @@
|
|
| 1 |
-
/* General Body & Layout */
|
| 2 |
-
body {
|
| 3 |
-
font-family: Arial, sans-serif;
|
| 4 |
-
margin: 0;
|
| 5 |
-
background-color: #f4f4f4;
|
| 6 |
-
color: #333;
|
| 7 |
-
}
|
| 8 |
-
|
| 9 |
-
.container {
|
| 10 |
-
width: 90%;
|
| 11 |
-
margin: 0 auto;
|
| 12 |
-
max-width: 1200px;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
/* Header */
|
| 16 |
-
header {
|
| 17 |
-
background-color: #2c3e50;
|
| 18 |
-
color: white;
|
| 19 |
-
padding: 1rem 0;
|
| 20 |
-
display: flex;
|
| 21 |
-
justify-content: space-between;
|
| 22 |
-
align-items: center;
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
header h1 {
|
| 26 |
-
margin: 0;
|
| 27 |
-
font-size: 1.8rem;
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
header nav a, header nav button {
|
| 31 |
-
color: rgb(64, 80, 104);
|
| 32 |
-
text-decoration: none;
|
| 33 |
-
background: #3498db;
|
| 34 |
-
padding: 0.5rem 1rem;
|
| 35 |
-
border-radius: 5px;
|
| 36 |
-
border: none;
|
| 37 |
-
cursor: pointer;
|
| 38 |
-
}
|
| 39 |
-
header nav span {
|
| 40 |
-
margin-right: 15px;
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
/* Product Grid */
|
| 44 |
-
.product-grid {
|
| 45 |
-
display: grid;
|
| 46 |
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
| 47 |
-
gap: 1.5rem;
|
| 48 |
-
margin-top: 2rem;
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
.product-card {
|
| 52 |
-
background-color: white;
|
| 53 |
-
border: 1px solid #ddd;
|
| 54 |
-
border-radius: 8px;
|
| 55 |
-
overflow: hidden;
|
| 56 |
-
text-align: center;
|
| 57 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 58 |
-
cursor: pointer;
|
| 59 |
-
transition: transform 0.2s, box-shadow 0.2s;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
.product-card:hover {
|
| 63 |
-
transform: translateY(-5px);
|
| 64 |
-
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
.product-card img {
|
| 68 |
-
width: 100%;
|
| 69 |
-
height: 180px;
|
| 70 |
-
object-fit: cover;
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
.product-card h3 {
|
| 74 |
-
margin: 0.5rem 0;
|
| 75 |
-
font-size: 1.1rem;
|
| 76 |
-
padding: 0 10px;
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
.product-card .price {
|
| 80 |
-
color: #e74c3c;
|
| 81 |
-
font-weight: bold;
|
| 82 |
-
font-size: 1.2rem;
|
| 83 |
-
margin-bottom: 1rem;
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
/* Auth Page */
|
| 87 |
-
.auth-body {
|
| 88 |
-
display: flex;
|
| 89 |
-
justify-content: center;
|
| 90 |
-
align-items: center;
|
| 91 |
-
height: 100vh;
|
| 92 |
-
background: linear-gradient(135deg, #3498db, #2c3e50);
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
.auth-container {
|
| 96 |
-
background-color: white;
|
| 97 |
-
padding: 2rem;
|
| 98 |
-
border-radius: 10px;
|
| 99 |
-
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
| 100 |
-
width: 100%;
|
| 101 |
-
max-width: 400px;
|
| 102 |
-
text-align: center;
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
.auth-container h2 {
|
| 106 |
-
margin-bottom: 1.5rem;
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
.auth-container input[type="text"],
|
| 110 |
-
.auth-container input[type="email"],
|
| 111 |
-
.auth-container input[type="password"] {
|
| 112 |
-
width: calc(100% - 20px);
|
| 113 |
-
padding: 10px;
|
| 114 |
-
margin-bottom: 1rem;
|
| 115 |
-
border: 1px solid #ccc;
|
| 116 |
-
border-radius: 5px;
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
.auth-container button {
|
| 120 |
-
width: 100%;
|
| 121 |
-
padding: 12px;
|
| 122 |
-
background-color: #2ecc71;
|
| 123 |
-
color: white;
|
| 124 |
-
border: none;
|
| 125 |
-
border-radius: 5px;
|
| 126 |
-
cursor: pointer;
|
| 127 |
-
font-size: 1rem;
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
.auth-container p {
|
| 131 |
-
margin-top: 1rem;
|
| 132 |
-
}
|
| 133 |
-
.auth-container a {
|
| 134 |
-
color: #3498db;
|
| 135 |
-
text-decoration: none;
|
| 136 |
-
font-weight: bold;
|
| 137 |
-
}
|
| 138 |
-
.user-type {
|
| 139 |
-
margin-bottom: 1rem;
|
| 140 |
-
text-align: left;
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
/* Dashboard */
|
| 144 |
-
.dashboard-form {
|
| 145 |
-
background: white;
|
| 146 |
-
padding: 1.5rem;
|
| 147 |
-
border-radius: 8px;
|
| 148 |
-
margin-bottom: 2rem;
|
| 149 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 150 |
-
}
|
| 151 |
-
.dashboard-form input, .dashboard-form textarea {
|
| 152 |
-
width: calc(100% - 22px);
|
| 153 |
-
padding: 10px;
|
| 154 |
-
margin-bottom: 10px;
|
| 155 |
-
border: 1px solid #ccc;
|
| 156 |
-
border-radius: 4px;
|
| 157 |
-
}
|
| 158 |
-
.dashboard-form button {
|
| 159 |
-
background-color: #2c3e50;
|
| 160 |
-
color: white;
|
| 161 |
-
padding: 10px 20px;
|
| 162 |
-
border: none;
|
| 163 |
-
border-radius: 4px;
|
| 164 |
-
cursor: pointer;
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
/* AI Assistant */
|
| 168 |
-
.ai-assistant {
|
| 169 |
-
background-color: #eaf5ff;
|
| 170 |
-
border: 1px solid #bde0ff;
|
| 171 |
-
border-radius: 8px;
|
| 172 |
-
padding: 1.5rem;
|
| 173 |
-
margin-bottom: 2rem;
|
| 174 |
-
}
|
| 175 |
-
|
| 176 |
-
.ai-assistant h3 {
|
| 177 |
-
margin-top: 0;
|
| 178 |
-
}
|
| 179 |
-
|
| 180 |
-
.ai-chat-box {
|
| 181 |
-
display: flex;
|
| 182 |
-
gap: 10px;
|
| 183 |
-
}
|
| 184 |
-
|
| 185 |
-
.ai-chat-box input {
|
| 186 |
-
flex-grow: 1;
|
| 187 |
-
padding: 10px;
|
| 188 |
-
border: 1px solid #ccc;
|
| 189 |
-
border-radius: 4px;
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
.ai-chat-box button {
|
| 193 |
-
padding: 10px 20px;
|
| 194 |
-
background-color: #3498db;
|
| 195 |
-
color: white;
|
| 196 |
-
border: none;
|
| 197 |
-
border-radius: 4px;
|
| 198 |
-
cursor: pointer;
|
| 199 |
-
}
|
| 200 |
-
|
| 201 |
-
#ai-results {
|
| 202 |
-
margin-top: 1rem;
|
| 203 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/js/cart.js
DELETED
|
File without changes
|
Eesha buying folder/js/loader.js
DELETED
|
File without changes
|
Eesha buying folder/js/product.js
DELETED
|
@@ -1,536 +0,0 @@
|
|
| 1 |
-
// Product detail page logic — uses EdgeFunctions (cookie-based auth, no Supabase SDK).
|
| 2 |
-
// Requires: ../js/config/edge-functions.js and ../js/cart.js loaded before this file.
|
| 3 |
-
|
| 4 |
-
// Get product ID from URL
|
| 5 |
-
function getProductIdFromUrl() {
|
| 6 |
-
const urlParams = new URLSearchParams(window.location.search);
|
| 7 |
-
return urlParams.get('id');
|
| 8 |
-
}
|
| 9 |
-
|
| 10 |
-
// Load product details via edge function
|
| 11 |
-
async function loadProductDetails() {
|
| 12 |
-
const productId = getProductIdFromUrl();
|
| 13 |
-
if (!productId) {
|
| 14 |
-
window.location.href = '/index.html';
|
| 15 |
-
return;
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
try {
|
| 19 |
-
// Show loading states
|
| 20 |
-
document.getElementById('loadingSkeleton').classList.remove('hidden');
|
| 21 |
-
document.getElementById('loadingText').classList.remove('hidden');
|
| 22 |
-
document.getElementById('productImagesContainer').classList.add('hidden');
|
| 23 |
-
document.getElementById('productInfo').classList.add('hidden');
|
| 24 |
-
document.getElementById('priceSection').classList.add('hidden');
|
| 25 |
-
document.getElementById('descriptionSection').classList.add('hidden');
|
| 26 |
-
|
| 27 |
-
// Fetch product via edge function
|
| 28 |
-
const result = await window.EdgeFunctions.products.byId(productId);
|
| 29 |
-
|
| 30 |
-
if (!result.success || !result.product) {
|
| 31 |
-
console.error('Error loading product:', result.error);
|
| 32 |
-
window.location.href = '/index.html';
|
| 33 |
-
return;
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
const product = result.product;
|
| 37 |
-
window._currentProduct = product; // cache for share functions
|
| 38 |
-
|
| 39 |
-
// Hide loading states
|
| 40 |
-
document.getElementById('loadingSkeleton').classList.add('hidden');
|
| 41 |
-
document.getElementById('loadingText').classList.add('hidden');
|
| 42 |
-
document.getElementById('productImagesContainer').classList.remove('hidden');
|
| 43 |
-
document.getElementById('productInfo').classList.remove('hidden');
|
| 44 |
-
document.getElementById('priceSection').classList.remove('hidden');
|
| 45 |
-
document.getElementById('descriptionSection').classList.remove('hidden');
|
| 46 |
-
|
| 47 |
-
// Update the UI with product details
|
| 48 |
-
document.getElementById('mainImage').src = product.image_url;
|
| 49 |
-
document.getElementById('productName').textContent = product.name;
|
| 50 |
-
document.getElementById('productCategory').textContent = product.category || 'General';
|
| 51 |
-
document.getElementById('productPrice').textContent = `₦${product.price.toFixed(2)}`;
|
| 52 |
-
document.getElementById('productDescription').textContent = product.description;
|
| 53 |
-
|
| 54 |
-
// Handle optional fields
|
| 55 |
-
if (product.original_price) {
|
| 56 |
-
document.getElementById('originalPrice').textContent = `₦${product.original_price.toFixed(2)}`;
|
| 57 |
-
const discount = Math.round(((product.original_price - product.price) / product.original_price) * 100);
|
| 58 |
-
document.getElementById('discountBadge').textContent = `-${discount}%`;
|
| 59 |
-
} else {
|
| 60 |
-
document.getElementById('originalPrice').classList.add('hidden');
|
| 61 |
-
document.getElementById('discountBadge').classList.add('hidden');
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
// Update page title
|
| 65 |
-
document.title = `${product.name} - Eesha`;
|
| 66 |
-
|
| 67 |
-
// Handle product images
|
| 68 |
-
const thumbnailContainer = document.getElementById('thumbnailContainer');
|
| 69 |
-
thumbnailContainer.innerHTML = ''; // Clear existing thumbnails
|
| 70 |
-
|
| 71 |
-
// Add main image to thumbnails
|
| 72 |
-
const mainThumbnail = createThumbnail(product.image_url, product.name, true);
|
| 73 |
-
thumbnailContainer.appendChild(mainThumbnail);
|
| 74 |
-
|
| 75 |
-
// Add additional images if available
|
| 76 |
-
if (product.additional_images && Array.isArray(product.additional_images)) {
|
| 77 |
-
product.additional_images.forEach(imgUrl => {
|
| 78 |
-
const thumbnail = createThumbnail(imgUrl, product.name);
|
| 79 |
-
thumbnailContainer.appendChild(thumbnail);
|
| 80 |
-
});
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
// Handle rating if available
|
| 84 |
-
if (product.rating) {
|
| 85 |
-
const ratingDiv = document.getElementById('productRating');
|
| 86 |
-
const stars = '★'.repeat(Math.floor(product.rating)) + '☆'.repeat(5 - Math.floor(product.rating));
|
| 87 |
-
ratingDiv.innerHTML = `
|
| 88 |
-
<div class="text-yellow-400">${stars}</div>
|
| 89 |
-
<span class="text-gray-600">(${product.rating_count || 0} reviews)</span>
|
| 90 |
-
`;
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
} catch (error) {
|
| 94 |
-
console.error('Error loading product:', error);
|
| 95 |
-
alert('Error loading product details. Please try again.');
|
| 96 |
-
}
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
// Quantity management
|
| 100 |
-
let quantity = 1;
|
| 101 |
-
|
| 102 |
-
function increaseQty() {
|
| 103 |
-
quantity++;
|
| 104 |
-
updateQuantityDisplay();
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
function decreaseQty() {
|
| 108 |
-
if (quantity > 1) {
|
| 109 |
-
quantity--;
|
| 110 |
-
updateQuantityDisplay();
|
| 111 |
-
}
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
function updateQuantityDisplay() {
|
| 115 |
-
document.getElementById('quantity').textContent = quantity;
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
// Add to cart functionality — uses Cart module (requires login, cookie-based)
|
| 119 |
-
async function addToCart() {
|
| 120 |
-
const productId = getProductIdFromUrl();
|
| 121 |
-
if (!productId) return;
|
| 122 |
-
|
| 123 |
-
try {
|
| 124 |
-
// Cart module handles auth check + cart upsert internally
|
| 125 |
-
const result = await window.Cart.addToCart(productId, quantity);
|
| 126 |
-
|
| 127 |
-
if (!result.success) {
|
| 128 |
-
if (result.requiresAuth) {
|
| 129 |
-
// Redirect to login
|
| 130 |
-
if (confirm('Please login to add items to your cart. Go to login page?')) {
|
| 131 |
-
window.location.href = `login.html?redirect=product&id=${productId}`;
|
| 132 |
-
}
|
| 133 |
-
return;
|
| 134 |
-
}
|
| 135 |
-
alert(result.message || 'Failed to add to cart. Please try again.');
|
| 136 |
-
return;
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
// Visual feedback
|
| 140 |
-
const addBtn = document.querySelector('button[onclick="addToCart()"]');
|
| 141 |
-
if (addBtn) {
|
| 142 |
-
const orig = addBtn.innerHTML;
|
| 143 |
-
addBtn.innerHTML = '<i class="fas fa-check"></i> Added!';
|
| 144 |
-
addBtn.classList.add('bg-green-500');
|
| 145 |
-
setTimeout(() => {
|
| 146 |
-
addBtn.innerHTML = orig;
|
| 147 |
-
addBtn.classList.remove('bg-green-500');
|
| 148 |
-
}, 1500);
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
} catch (error) {
|
| 152 |
-
console.error('Add to cart failed:', error);
|
| 153 |
-
alert('Failed to add to cart. Please try again.');
|
| 154 |
-
}
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
// Update cart count in header — delegates to shared Cart module
|
| 158 |
-
async function updateCartCount() {
|
| 159 |
-
try {
|
| 160 |
-
if (window.Cart && window.Cart.updateCartCountUI) {
|
| 161 |
-
await window.Cart.updateCartCountUI();
|
| 162 |
-
}
|
| 163 |
-
} catch (err) {
|
| 164 |
-
console.error('Error updating cart count:', err);
|
| 165 |
-
}
|
| 166 |
-
}
|
| 167 |
-
|
| 168 |
-
// Image gallery functionality
|
| 169 |
-
function createThumbnail(imageUrl, altText, isActive = false) {
|
| 170 |
-
const div = document.createElement('div');
|
| 171 |
-
div.className = `cursor-pointer rounded-lg overflow-hidden border-2 ${isActive ? 'border-brand-500' : 'border-transparent'}`;
|
| 172 |
-
|
| 173 |
-
const img = document.createElement('img');
|
| 174 |
-
img.src = imageUrl;
|
| 175 |
-
img.alt = altText;
|
| 176 |
-
img.className = 'w-full h-full object-cover aspect-square';
|
| 177 |
-
|
| 178 |
-
div.appendChild(img);
|
| 179 |
-
|
| 180 |
-
div.addEventListener('click', () => {
|
| 181 |
-
// Update main image
|
| 182 |
-
updateMainImage(imageUrl);
|
| 183 |
-
|
| 184 |
-
// Update active state of thumbnails
|
| 185 |
-
document.querySelectorAll('#thumbnailContainer > div').forEach(thumb => {
|
| 186 |
-
thumb.classList.remove('border-brand-500');
|
| 187 |
-
thumb.classList.add('border-transparent');
|
| 188 |
-
});
|
| 189 |
-
div.classList.remove('border-transparent');
|
| 190 |
-
div.classList.add('border-brand-500');
|
| 191 |
-
});
|
| 192 |
-
|
| 193 |
-
return div;
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
function updateMainImage(imageUrl) {
|
| 197 |
-
const mainImage = document.getElementById('mainImage');
|
| 198 |
-
mainImage.src = imageUrl;
|
| 199 |
-
}
|
| 200 |
-
|
| 201 |
-
// Global user state (kept in memory only — no localStorage)
|
| 202 |
-
let currentUser = null;
|
| 203 |
-
|
| 204 |
-
// With cookie-based auth there is no guest cart in localStorage to merge.
|
| 205 |
-
// This function is retained as a no-op for backwards compatibility with the
|
| 206 |
-
// existing init flow.
|
| 207 |
-
async function mergeGuestCartAfterSignIn() {
|
| 208 |
-
return;
|
| 209 |
-
}
|
| 210 |
-
|
| 211 |
-
function updateUIforAuthState(user) {
|
| 212 |
-
const loginLink = document.getElementById('login-link');
|
| 213 |
-
const signupLink = document.getElementById('signup-link');
|
| 214 |
-
const userMenu = document.getElementById('user-menu');
|
| 215 |
-
const userEmail = document.getElementById('user-email');
|
| 216 |
-
const mobileLogin = document.querySelector('#mobile-auth-links a[href="login.html"]') || document.querySelector('#mobile-auth-links a[href="Eesha buying folder/login.html"]');
|
| 217 |
-
const mobileSignup = document.querySelector('#mobile-auth-links a[href="signup.html"]') || document.querySelector('#mobile-auth-links a[href="Eesha buying folder/signup.html"]');
|
| 218 |
-
const mobileLogout = document.getElementById('mobile-logout-button');
|
| 219 |
-
|
| 220 |
-
if (user) {
|
| 221 |
-
if (loginLink) loginLink.style.display = 'none';
|
| 222 |
-
if (signupLink) signupLink.style.display = 'none';
|
| 223 |
-
if (userMenu) userMenu.style.display = 'block';
|
| 224 |
-
if (userEmail) userEmail.textContent = user.email || '';
|
| 225 |
-
if (mobileLogin) mobileLogin.style.display = 'none';
|
| 226 |
-
if (mobileSignup) mobileSignup.style.display = 'none';
|
| 227 |
-
if (mobileLogout) mobileLogout.style.display = 'block';
|
| 228 |
-
} else {
|
| 229 |
-
if (loginLink) loginLink.style.display = 'block';
|
| 230 |
-
if (signupLink) signupLink.style.display = 'block';
|
| 231 |
-
if (userMenu) userMenu.style.display = 'none';
|
| 232 |
-
if (mobileLogin) mobileLogin.style.display = 'block';
|
| 233 |
-
if (mobileSignup) mobileSignup.style.display = 'block';
|
| 234 |
-
if (mobileLogout) mobileLogout.style.display = 'none';
|
| 235 |
-
}
|
| 236 |
-
}
|
| 237 |
-
|
| 238 |
-
// Initialize page and auth handling
|
| 239 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 240 |
-
// Restore session via cookie-based auth (no onAuthStateChange available)
|
| 241 |
-
try {
|
| 242 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 243 |
-
currentUser = user || null;
|
| 244 |
-
updateUIforAuthState(currentUser);
|
| 245 |
-
await updateCartCount();
|
| 246 |
-
} catch (err) {
|
| 247 |
-
console.error('Error checking initial session:', err);
|
| 248 |
-
}
|
| 249 |
-
|
| 250 |
-
// Wire up logout buttons if present (cookie-based logout clears server cookie)
|
| 251 |
-
const logoutButton = document.getElementById('logout-button');
|
| 252 |
-
const mobileLogoutButton = document.getElementById('mobile-logout-button');
|
| 253 |
-
if (logoutButton) logoutButton.addEventListener('click', async (e) => {
|
| 254 |
-
e.preventDefault();
|
| 255 |
-
await window.EdgeFunctions.auth.logout();
|
| 256 |
-
currentUser = null;
|
| 257 |
-
updateUIforAuthState(null);
|
| 258 |
-
await updateCartCount();
|
| 259 |
-
window.location.reload();
|
| 260 |
-
});
|
| 261 |
-
if (mobileLogoutButton) mobileLogoutButton.addEventListener('click', async (e) => {
|
| 262 |
-
e.preventDefault();
|
| 263 |
-
await window.EdgeFunctions.auth.logout();
|
| 264 |
-
currentUser = null;
|
| 265 |
-
updateUIforAuthState(null);
|
| 266 |
-
await updateCartCount();
|
| 267 |
-
window.location.reload();
|
| 268 |
-
});
|
| 269 |
-
|
| 270 |
-
// User menu toggle
|
| 271 |
-
const userMenuButton = document.getElementById('user-menu-button');
|
| 272 |
-
const userMenuDropdown = document.getElementById('user-menu-dropdown');
|
| 273 |
-
if (userMenuButton && userMenuDropdown) {
|
| 274 |
-
userMenuButton.addEventListener('click', () => userMenuDropdown.classList.toggle('hidden'));
|
| 275 |
-
document.addEventListener('click', (e) => {
|
| 276 |
-
if (!document.getElementById('user-menu').contains(e.target)) {
|
| 277 |
-
userMenuDropdown.classList.add('hidden');
|
| 278 |
-
}
|
| 279 |
-
});
|
| 280 |
-
}
|
| 281 |
-
|
| 282 |
-
// Mobile menu
|
| 283 |
-
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
|
| 284 |
-
const mobileMenu = document.getElementById('mobileMenu');
|
| 285 |
-
if (mobileMenuBtn && mobileMenu) mobileMenuBtn.addEventListener('click', () => mobileMenu.classList.toggle('hidden'));
|
| 286 |
-
|
| 287 |
-
// Finally load product details and update cart count
|
| 288 |
-
await loadProductDetails();
|
| 289 |
-
await updateCartCount();
|
| 290 |
-
|
| 291 |
-
// Phase 2: load reviews + active group buys for this product
|
| 292 |
-
loadProductReviews();
|
| 293 |
-
loadActiveGroupBuys();
|
| 294 |
-
});
|
| 295 |
-
|
| 296 |
-
// Search bar behavior (navigate to search-result.html with query)
|
| 297 |
-
(function setupSearch() {
|
| 298 |
-
try {
|
| 299 |
-
const searchInput = document.getElementById('searchInput');
|
| 300 |
-
if (!searchInput) return;
|
| 301 |
-
|
| 302 |
-
const goToSearch = () => {
|
| 303 |
-
const q = (searchInput.value || '').trim();
|
| 304 |
-
if (!q) return;
|
| 305 |
-
// keep relative path consistent with index/product location
|
| 306 |
-
window.location.href = `../search-result.html?q=${encodeURIComponent(q)}`;
|
| 307 |
-
};
|
| 308 |
-
|
| 309 |
-
// Enter key
|
| 310 |
-
searchInput.addEventListener('keypress', (e) => {
|
| 311 |
-
if (e.key === 'Enter') goToSearch();
|
| 312 |
-
});
|
| 313 |
-
|
| 314 |
-
// If there's an adjacent button (search icon) try to attach click
|
| 315 |
-
const btn = searchInput.parentElement?.querySelector('button');
|
| 316 |
-
if (btn) btn.addEventListener('click', goToSearch);
|
| 317 |
-
} catch (err) {
|
| 318 |
-
console.error('Search setup failed:', err);
|
| 319 |
-
}
|
| 320 |
-
})();
|
| 321 |
-
|
| 322 |
-
// ====================================================================
|
| 323 |
-
// Phase 2: Product sharing, group buy, reviews
|
| 324 |
-
// ====================================================================
|
| 325 |
-
function getCurrentProductId() {
|
| 326 |
-
return getProductIdFromUrl();
|
| 327 |
-
}
|
| 328 |
-
|
| 329 |
-
function getCurrentProduct() {
|
| 330 |
-
// Stored when loadProductDetails runs
|
| 331 |
-
return window._currentProduct || null;
|
| 332 |
-
}
|
| 333 |
-
|
| 334 |
-
// ---- Sharing ----
|
| 335 |
-
function shareProduct(platform) {
|
| 336 |
-
const id = getCurrentProductId();
|
| 337 |
-
if (!id) return;
|
| 338 |
-
const url = `${window.location.origin}/Eesha%20buying%20folder/product.html?id=${id}`;
|
| 339 |
-
const product = getCurrentProduct();
|
| 340 |
-
const text = product
|
| 341 |
-
? `Check out "${product.name}" on Cellex — $${Number(product.price).toFixed(2)}`
|
| 342 |
-
: 'Check out this product on Cellex';
|
| 343 |
-
|
| 344 |
-
if (platform === 'whatsapp') {
|
| 345 |
-
window.open(`https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`, '_blank');
|
| 346 |
-
} else if (platform === 'telegram') {
|
| 347 |
-
window.open(`https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`, '_blank');
|
| 348 |
-
}
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
-
async function copyShareLink() {
|
| 352 |
-
const id = getCurrentProductId();
|
| 353 |
-
if (!id) return;
|
| 354 |
-
const url = `${window.location.origin}/Eesha%20buying%20folder/product.html?id=${id}`;
|
| 355 |
-
try {
|
| 356 |
-
await navigator.clipboard.writeText(url);
|
| 357 |
-
const btn = event?.target?.closest('button');
|
| 358 |
-
if (btn) {
|
| 359 |
-
const orig = btn.innerHTML;
|
| 360 |
-
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
| 361 |
-
setTimeout(() => btn.innerHTML = orig, 1500);
|
| 362 |
-
}
|
| 363 |
-
} catch (e) {
|
| 364 |
-
window.prompt('Copy this link:', url);
|
| 365 |
-
}
|
| 366 |
-
}
|
| 367 |
-
|
| 368 |
-
// ---- Group buy ----
|
| 369 |
-
async function startGroupBuy() {
|
| 370 |
-
const productId = getCurrentProductId();
|
| 371 |
-
if (!productId) return;
|
| 372 |
-
if (!currentUser) {
|
| 373 |
-
if (confirm('Please login to start a group buy. Go to login page?')) {
|
| 374 |
-
window.location.href = `login.html?next=${encodeURIComponent('/Eesha buying folder/product.html?id=' + productId)}`;
|
| 375 |
-
}
|
| 376 |
-
return;
|
| 377 |
-
}
|
| 378 |
-
if (!confirm('Start a group buy?\n\nGet 3 friends to join and everyone gets 20% off.\n\nWe will generate a share link for you.')) return;
|
| 379 |
-
const r = await window.EdgeFunctions.groupBuy.create(Number(productId), 3, 20);
|
| 380 |
-
if (r.success) {
|
| 381 |
-
const url = `${window.location.origin}/group-buy.html?id=${r.groupBuy.id}`;
|
| 382 |
-
const shareText = `🛍️ Join my group buy on Cellex! Get 20% off when 3 of us join. ${url}`;
|
| 383 |
-
window.open(`https://wa.me/?text=${encodeURIComponent(shareText)}`, '_blank');
|
| 384 |
-
// Also navigate to the group buy page
|
| 385 |
-
window.location.href = `/group-buy.html?id=${r.groupBuy.id}`;
|
| 386 |
-
} else {
|
| 387 |
-
alert('Failed to start group buy: ' + (r.error || 'Unknown error'));
|
| 388 |
-
}
|
| 389 |
-
}
|
| 390 |
-
|
| 391 |
-
async function loadActiveGroupBuys() {
|
| 392 |
-
const productId = getCurrentProductId();
|
| 393 |
-
if (!productId) return;
|
| 394 |
-
const container = document.getElementById('activeGroupBuys');
|
| 395 |
-
if (!container) return;
|
| 396 |
-
try {
|
| 397 |
-
const r = await window.EdgeFunctions.groupBuy.active(Number(productId));
|
| 398 |
-
if (!r.success || !r.groupBuys || r.groupBuys.length === 0) return;
|
| 399 |
-
container.classList.remove('hidden');
|
| 400 |
-
container.innerHTML = `
|
| 401 |
-
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3">
|
| 402 |
-
<div class="text-xs font-bold text-amber-700 uppercase tracking-wide mb-2">
|
| 403 |
-
<i class="fas fa-fire"></i> ${r.groupBuys.length} active group buy${r.groupBuys.length > 1 ? 's' : ''} — join & get 20% off!
|
| 404 |
-
</div>
|
| 405 |
-
${r.groupBuys.map(gb => `
|
| 406 |
-
<a href="/group-buy.html?id=${gb.id}" class="block text-sm text-amber-900 hover:underline">
|
| 407 |
-
👥 ${gb.current_count}/${gb.target_count} joined · expires ${new Date(gb.expires_at).toLocaleString()}
|
| 408 |
-
</a>
|
| 409 |
-
`).join('')}
|
| 410 |
-
</div>`;
|
| 411 |
-
} catch (e) { /* silent fail */ }
|
| 412 |
-
}
|
| 413 |
-
|
| 414 |
-
// ---- Reviews ----
|
| 415 |
-
async function loadProductReviews() {
|
| 416 |
-
const productId = getCurrentProductId();
|
| 417 |
-
if (!productId) return;
|
| 418 |
-
try {
|
| 419 |
-
const r = await window.EdgeFunctions.reviews.byProduct(Number(productId));
|
| 420 |
-
if (!r.success) return;
|
| 421 |
-
|
| 422 |
-
// Summary
|
| 423 |
-
const summary = document.getElementById('reviewsSummary');
|
| 424 |
-
if (r.summary.count > 0) {
|
| 425 |
-
summary.classList.remove('hidden');
|
| 426 |
-
document.getElementById('reviewsAvg').textContent = Number(r.summary.avg).toFixed(1);
|
| 427 |
-
document.getElementById('reviewsStars').textContent =
|
| 428 |
-
'★'.repeat(Math.round(r.summary.avg)) + '☆'.repeat(5 - Math.round(r.summary.avg));
|
| 429 |
-
document.getElementById('reviewsCount').textContent = `${r.summary.count} review${r.summary.count > 1 ? 's' : ''}`;
|
| 430 |
-
document.getElementById('reviewsBlurb').textContent =
|
| 431 |
-
r.summary.avg >= 4 ? 'Buyers love this product!' :
|
| 432 |
-
r.summary.avg >= 3 ? 'Most buyers are satisfied.' :
|
| 433 |
-
'Mixed reviews — read below.';
|
| 434 |
-
}
|
| 435 |
-
|
| 436 |
-
// List
|
| 437 |
-
const list = document.getElementById('reviewsList');
|
| 438 |
-
if (!r.reviews || r.reviews.length === 0) {
|
| 439 |
-
list.innerHTML = '<div class="text-gray-500 text-sm">No reviews yet. Be the first to write one!</div>';
|
| 440 |
-
return;
|
| 441 |
-
}
|
| 442 |
-
list.innerHTML = r.reviews.map(rev => `
|
| 443 |
-
<div class="border-b pb-3">
|
| 444 |
-
<div class="flex items-center justify-between">
|
| 445 |
-
<div class="flex items-center gap-2">
|
| 446 |
-
<div class="w-8 h-8 rounded-full bg-amber-100 text-amber-700 flex items-center justify-center font-bold text-sm">
|
| 447 |
-
${(rev.reviewer_name || 'B').charAt(0).toUpperCase()}
|
| 448 |
-
</div>
|
| 449 |
-
<div>
|
| 450 |
-
<div class="font-medium text-sm">${escapeHtml(rev.reviewer_name || 'Buyer')}</div>
|
| 451 |
-
<div class="text-xs text-gray-500">${new Date(rev.created_at).toLocaleDateString()}</div>
|
| 452 |
-
</div>
|
| 453 |
-
</div>
|
| 454 |
-
<div class="text-yellow-400 text-sm">${'★'.repeat(rev.rating)}${'☆'.repeat(5 - rev.rating)}</div>
|
| 455 |
-
</div>
|
| 456 |
-
${rev.title ? `<div class="font-semibold mt-2">${escapeHtml(rev.title)}</div>` : ''}
|
| 457 |
-
${rev.comment ? `<p class="text-gray-700 text-sm mt-1">${escapeHtml(rev.comment)}</p>` : ''}
|
| 458 |
-
${rev.verified_purchase ? '<div class="text-xs text-green-600 mt-1"><i class="fas fa-check-circle"></i> Verified purchase</div>' : ''}
|
| 459 |
-
${rev.images && rev.images.length ? `<div class="flex gap-2 mt-2">${rev.images.map(img => `<img src="${escapeHtml(img)}" class="w-16 h-16 object-cover rounded">`).join('')}</div>` : ''}
|
| 460 |
-
<button onclick="markHelpful('${rev.id}')" class="text-xs text-gray-500 hover:text-amber-600 mt-2">
|
| 461 |
-
<i class="fas fa-thumbs-up"></i> Helpful (${rev.helpful_count || 0})
|
| 462 |
-
</button>
|
| 463 |
-
</div>`).join('');
|
| 464 |
-
} catch (e) {
|
| 465 |
-
console.error('reviews load error', e);
|
| 466 |
-
}
|
| 467 |
-
}
|
| 468 |
-
|
| 469 |
-
function escapeHtml(s) {
|
| 470 |
-
return String(s ?? '').replace(/[&<>"']/g, c => ({
|
| 471 |
-
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
| 472 |
-
}[c]));
|
| 473 |
-
}
|
| 474 |
-
|
| 475 |
-
// ---- Review modal ----
|
| 476 |
-
function openReviewModal() {
|
| 477 |
-
if (!currentUser) {
|
| 478 |
-
if (confirm('Please login to write a review. Go to login page?')) {
|
| 479 |
-
const id = getCurrentProductId();
|
| 480 |
-
window.location.href = `login.html?next=${encodeURIComponent('/Eesha buying folder/product.html?id=' + id)}`;
|
| 481 |
-
}
|
| 482 |
-
return;
|
| 483 |
-
}
|
| 484 |
-
document.getElementById('reviewModal').classList.remove('hidden');
|
| 485 |
-
// Star picker
|
| 486 |
-
document.querySelectorAll('#starPicker button').forEach(btn => {
|
| 487 |
-
btn.onclick = () => {
|
| 488 |
-
const star = parseInt(btn.dataset.star);
|
| 489 |
-
document.getElementById('reviewRating').value = star;
|
| 490 |
-
document.querySelectorAll('#starPicker button').forEach(b => {
|
| 491 |
-
b.classList.remove('text-amber-400');
|
| 492 |
-
b.classList.add('text-gray-300');
|
| 493 |
-
});
|
| 494 |
-
for (let i = 1; i <= star; i++) {
|
| 495 |
-
const b = document.querySelector(`#starPicker button[data-star="${i}"]`);
|
| 496 |
-
if (b) { b.classList.add('text-amber-400'); b.classList.remove('text-gray-300'); }
|
| 497 |
-
}
|
| 498 |
-
};
|
| 499 |
-
});
|
| 500 |
-
document.getElementById('reviewForm').onsubmit = submitReview;
|
| 501 |
-
}
|
| 502 |
-
|
| 503 |
-
function closeReviewModal() {
|
| 504 |
-
document.getElementById('reviewModal').classList.add('hidden');
|
| 505 |
-
}
|
| 506 |
-
|
| 507 |
-
async function submitReview(e) {
|
| 508 |
-
e.preventDefault();
|
| 509 |
-
const errEl = document.getElementById('reviewError');
|
| 510 |
-
errEl.classList.add('hidden');
|
| 511 |
-
const rating = parseInt(document.getElementById('reviewRating').value);
|
| 512 |
-
if (!rating || rating < 1 || rating > 5) {
|
| 513 |
-
errEl.textContent = 'Please select a rating (1-5 stars)';
|
| 514 |
-
errEl.classList.remove('hidden');
|
| 515 |
-
return;
|
| 516 |
-
}
|
| 517 |
-
const r = await window.EdgeFunctions.reviews.create({
|
| 518 |
-
productId: Number(getCurrentProductId()),
|
| 519 |
-
rating,
|
| 520 |
-
title: document.getElementById('reviewTitle').value.trim(),
|
| 521 |
-
comment: document.getElementById('reviewComment').value.trim(),
|
| 522 |
-
});
|
| 523 |
-
if (r.success) {
|
| 524 |
-
closeReviewModal();
|
| 525 |
-
await loadProductReviews();
|
| 526 |
-
alert('Thanks for your review!');
|
| 527 |
-
} else {
|
| 528 |
-
errEl.textContent = r.error || 'Failed to submit review';
|
| 529 |
-
errEl.classList.remove('hidden');
|
| 530 |
-
}
|
| 531 |
-
}
|
| 532 |
-
|
| 533 |
-
async function markHelpful(reviewId) {
|
| 534 |
-
const r = await window.EdgeFunctions.reviews.helpful(reviewId);
|
| 535 |
-
if (r.success) await loadProductReviews();
|
| 536 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/login.html
DELETED
|
@@ -1,153 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Login - Cellex</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 9 |
-
<style>
|
| 10 |
-
.loading {
|
| 11 |
-
animation: spin 1s linear infinite;
|
| 12 |
-
}
|
| 13 |
-
@keyframes spin {
|
| 14 |
-
0% { transform: rotate(0deg); }
|
| 15 |
-
100% { transform: rotate(360deg); }
|
| 16 |
-
}
|
| 17 |
-
</style>
|
| 18 |
-
</head>
|
| 19 |
-
<body class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
|
| 20 |
-
<!-- Main Container -->
|
| 21 |
-
<div class="w-full max-w-md">
|
| 22 |
-
<!-- Logo -->
|
| 23 |
-
<div class="text-center mb-8">
|
| 24 |
-
<a href="../index.html" class="inline-block">
|
| 25 |
-
<h1 class="text-4xl font-extrabold text-[#f0474a]">Cellex</h1>
|
| 26 |
-
</a>
|
| 27 |
-
<p class="text-gray-500 mt-2">Nigeria's #1 Marketplace</p>
|
| 28 |
-
</div>
|
| 29 |
-
|
| 30 |
-
<!-- Login Card -->
|
| 31 |
-
<div class="bg-white rounded-2xl shadow-lg p-8">
|
| 32 |
-
<h2 class="text-2xl font-bold text-gray-800 mb-2">Welcome back</h2>
|
| 33 |
-
<p class="text-gray-500 mb-6">Please sign in to continue</p>
|
| 34 |
-
|
| 35 |
-
<!-- Error Message -->
|
| 36 |
-
<div id="errorMessage" class="hidden bg-red-50 border border-red-200 text-red-600 rounded-lg p-3 mb-4 text-sm"></div>
|
| 37 |
-
|
| 38 |
-
<!-- Login Form -->
|
| 39 |
-
<form id="loginForm">
|
| 40 |
-
<div class="mb-4">
|
| 41 |
-
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email address</label>
|
| 42 |
-
<input type="email" id="email" name="email" required
|
| 43 |
-
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#f0474a] focus:border-transparent"
|
| 44 |
-
placeholder="you@example.com">
|
| 45 |
-
</div>
|
| 46 |
-
|
| 47 |
-
<div class="mb-4">
|
| 48 |
-
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
| 49 |
-
<input type="password" id="password" name="password" required
|
| 50 |
-
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#f0474a] focus:border-transparent"
|
| 51 |
-
placeholder="••••••••">
|
| 52 |
-
</div>
|
| 53 |
-
|
| 54 |
-
<div class="flex items-center justify-between mb-6">
|
| 55 |
-
<label class="flex items-center">
|
| 56 |
-
<input type="checkbox" id="remember" class="rounded border-gray-300 text-[#f0474a] focus:ring-[#f0474a]">
|
| 57 |
-
<span class="ml-2 text-sm text-gray-600">Remember me</span>
|
| 58 |
-
</label>
|
| 59 |
-
<a href="#" class="text-sm text-[#f0474a] hover:underline">Forgot password?</a>
|
| 60 |
-
</div>
|
| 61 |
-
|
| 62 |
-
<button type="submit" id="loginButton"
|
| 63 |
-
class="w-full bg-[#f0474a] text-white py-3 rounded-lg font-semibold hover:bg-[#d93e41] transition-colors flex items-center justify-center gap-2">
|
| 64 |
-
<svg class="loading hidden animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
| 65 |
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
| 66 |
-
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 67 |
-
</svg>
|
| 68 |
-
Sign in
|
| 69 |
-
</button>
|
| 70 |
-
</form>
|
| 71 |
-
|
| 72 |
-
<div class="mt-6 text-center">
|
| 73 |
-
<p class="text-sm text-gray-600">
|
| 74 |
-
Don't have an account?
|
| 75 |
-
<a href="signup.html" class="font-medium text-[#f0474a] hover:underline">Sign up</a>
|
| 76 |
-
</p>
|
| 77 |
-
</div>
|
| 78 |
-
</div>
|
| 79 |
-
</div>
|
| 80 |
-
|
| 81 |
-
<!-- Scripts: Edge function client only (NO Supabase SDK) -->
|
| 82 |
-
<script src="../js/config/edge-functions.js?v=2"></script>
|
| 83 |
-
<script>
|
| 84 |
-
// Elements
|
| 85 |
-
const loginForm = document.getElementById('loginForm');
|
| 86 |
-
const loginButton = document.getElementById('loginButton');
|
| 87 |
-
const errorMessage = document.getElementById('errorMessage');
|
| 88 |
-
const loadingIcon = loginButton.querySelector('.loading');
|
| 89 |
-
|
| 90 |
-
// Helper Functions
|
| 91 |
-
function showError(message) {
|
| 92 |
-
errorMessage.textContent = message;
|
| 93 |
-
errorMessage.classList.remove('hidden');
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
function hideError() {
|
| 97 |
-
errorMessage.classList.add('hidden');
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
function setLoading(isLoading) {
|
| 101 |
-
loadingIcon.classList.toggle('hidden', !isLoading);
|
| 102 |
-
loginButton.disabled = isLoading;
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
// Email/Password Login via edge function (no Supabase SDK)
|
| 106 |
-
async function performLogin() {
|
| 107 |
-
hideError();
|
| 108 |
-
setLoading(true);
|
| 109 |
-
|
| 110 |
-
const email = document.getElementById('email').value;
|
| 111 |
-
const password = document.getElementById('password').value;
|
| 112 |
-
|
| 113 |
-
try {
|
| 114 |
-
const result = await window.EdgeFunctions.auth.login(email, password);
|
| 115 |
-
|
| 116 |
-
if (!result.success) {
|
| 117 |
-
showError(result.error || 'Incorrect email or password. New user? Please sign up.');
|
| 118 |
-
return;
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
// Login successful — redirect to ?next= param if present, else home
|
| 122 |
-
const params = new URLSearchParams(window.location.search);
|
| 123 |
-
const next = params.get('next');
|
| 124 |
-
window.location.href = next || '../index.html';
|
| 125 |
-
} catch (error) {
|
| 126 |
-
showError(error.message || 'Failed to sign in');
|
| 127 |
-
} finally {
|
| 128 |
-
setLoading(false);
|
| 129 |
-
}
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
loginForm.addEventListener('submit', async (e) => {
|
| 133 |
-
e.preventDefault();
|
| 134 |
-
await performLogin();
|
| 135 |
-
});
|
| 136 |
-
|
| 137 |
-
// Check session on load (via edge function — if already logged in, redirect)
|
| 138 |
-
window.addEventListener('load', async () => {
|
| 139 |
-
try {
|
| 140 |
-
const result = await window.EdgeFunctions.auth.checkSession();
|
| 141 |
-
if (result.success && result.user) {
|
| 142 |
-
// Already logged in — redirect to ?next= param if present, else home
|
| 143 |
-
const params = new URLSearchParams(window.location.search);
|
| 144 |
-
const next = params.get('next');
|
| 145 |
-
window.location.href = next || '../index.html';
|
| 146 |
-
}
|
| 147 |
-
} catch (e) {
|
| 148 |
-
// Not logged in — stay on login page
|
| 149 |
-
}
|
| 150 |
-
});
|
| 151 |
-
</script>
|
| 152 |
-
</body>
|
| 153 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/order-confirmation.html
DELETED
|
@@ -1,450 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Order Confirmation - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* {
|
| 44 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
@keyframes confetti {
|
| 48 |
-
0% { transform: translateY(0) rotateZ(0); opacity: 1; }
|
| 49 |
-
100% { transform: translateY(100vh) rotateZ(720deg); opacity: 0; }
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
@keyframes bounce {
|
| 53 |
-
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
|
| 54 |
-
40% { transform: translateY(-20px); }
|
| 55 |
-
60% { transform: translateY(-10px); }
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
@keyframes fadeInUp {
|
| 59 |
-
from { opacity: 0; transform: translateY(30px); }
|
| 60 |
-
to { opacity: 1; transform: translateY(0); }
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
@keyframes scaleIn {
|
| 64 |
-
from { transform: scale(0); opacity: 0; }
|
| 65 |
-
to { transform: scale(1); opacity: 1; }
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
.confetti {
|
| 69 |
-
position: fixed;
|
| 70 |
-
pointer-events: none;
|
| 71 |
-
z-index: 100;
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
.fade-in-up {
|
| 75 |
-
animation: fadeInUp 0.6s ease-out forwards;
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
.scale-in {
|
| 79 |
-
animation: scaleIn 0.5s ease-out forwards;
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
.bounce {
|
| 83 |
-
animation: bounce 1s ease-out;
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
.progress-step {
|
| 87 |
-
transition: all 0.3s ease;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
.progress-step.active .step-circle {
|
| 91 |
-
background: #f59e0b;
|
| 92 |
-
color: white;
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
.progress-step.completed .step-circle {
|
| 96 |
-
background: #22c55e;
|
| 97 |
-
color: white;
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
.progress-line {
|
| 101 |
-
height: 2px;
|
| 102 |
-
background: #e5e7eb;
|
| 103 |
-
transition: background 0.3s ease;
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
.progress-line.completed {
|
| 107 |
-
background: #22c55e;
|
| 108 |
-
}
|
| 109 |
-
</style>
|
| 110 |
-
</head>
|
| 111 |
-
<body class="bg-gray-50 min-h-screen">
|
| 112 |
-
<!-- Loading State -->
|
| 113 |
-
<div id="loading" class="flex items-center justify-center min-h-screen">
|
| 114 |
-
<div class="text-center">
|
| 115 |
-
<div class="animate-spin rounded-full h-12 w-12 border-4 border-brand-500 border-t-transparent mx-auto mb-4"></div>
|
| 116 |
-
<p class="text-gray-500">Loading order details...</p>
|
| 117 |
-
</div>
|
| 118 |
-
</div>
|
| 119 |
-
|
| 120 |
-
<!-- Main Content -->
|
| 121 |
-
<div id="content" class="hidden">
|
| 122 |
-
<!-- Header -->
|
| 123 |
-
<header class="bg-white shadow-sm">
|
| 124 |
-
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 125 |
-
<div class="flex justify-between items-center h-16">
|
| 126 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 127 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 128 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 129 |
-
<defs>
|
| 130 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 131 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 132 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 133 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 134 |
-
</linearGradient>
|
| 135 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 136 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 137 |
-
<feMerge>
|
| 138 |
-
<feMergeNode in="coloredBlur"/>
|
| 139 |
-
<feMergeNode in="SourceGraphic"/>
|
| 140 |
-
</feMerge>
|
| 141 |
-
</filter>
|
| 142 |
-
</defs>
|
| 143 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 144 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 145 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 146 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 147 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 148 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 149 |
-
<g transform="translate(52, 8)">
|
| 150 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 151 |
-
</g>
|
| 152 |
-
</svg>
|
| 153 |
-
</div>
|
| 154 |
-
<div class="flex flex-col">
|
| 155 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 156 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 157 |
-
</div>
|
| 158 |
-
</a>
|
| 159 |
-
<a href="../index.html" class="text-gray-600 hover:text-brand-500 transition-colors">
|
| 160 |
-
<i class="fas fa-home text-xl"></i>
|
| 161 |
-
</a>
|
| 162 |
-
</div>
|
| 163 |
-
</nav>
|
| 164 |
-
</header>
|
| 165 |
-
|
| 166 |
-
<main class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
| 167 |
-
<!-- Success Banner -->
|
| 168 |
-
<div class="text-center mb-12 fade-in-up">
|
| 169 |
-
<div class="w-24 h-24 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6 bounce">
|
| 170 |
-
<div class="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center">
|
| 171 |
-
<i class="fas fa-check text-white text-3xl"></i>
|
| 172 |
-
</div>
|
| 173 |
-
</div>
|
| 174 |
-
<h1 class="text-4xl font-bold text-gray-900 mb-2">Order Confirmed!</h1>
|
| 175 |
-
<p class="text-gray-500 text-lg">Thank you for your purchase</p>
|
| 176 |
-
</div>
|
| 177 |
-
|
| 178 |
-
<!-- Order Details Card -->
|
| 179 |
-
<div class="bg-white rounded-2xl shadow-lg overflow-hidden mb-8 fade-in-up" style="animation-delay: 0.2s">
|
| 180 |
-
<div class="bg-gradient-to-r from-primary to-red-500 px-6 py-4">
|
| 181 |
-
<div class="flex items-center justify-between">
|
| 182 |
-
<div>
|
| 183 |
-
<p class="text-white/80 text-sm">Order Number</p>
|
| 184 |
-
<p class="text-white text-xl font-bold" id="order-number"># Loading...</p>
|
| 185 |
-
</div>
|
| 186 |
-
<div class="text-right">
|
| 187 |
-
<p class="text-white/80 text-sm">Order Date</p>
|
| 188 |
-
<p class="text-white font-medium" id="order-date">Loading...</p>
|
| 189 |
-
</div>
|
| 190 |
-
</div>
|
| 191 |
-
</div>
|
| 192 |
-
|
| 193 |
-
<div class="p-6">
|
| 194 |
-
<!-- Order Status Progress -->
|
| 195 |
-
<div class="mb-8">
|
| 196 |
-
<h3 class="font-semibold text-gray-900 mb-4">Order Status</h3>
|
| 197 |
-
<div class="flex items-center justify-between">
|
| 198 |
-
<div class="flex-1 text-center">
|
| 199 |
-
<div class="progress-step active">
|
| 200 |
-
<div class="step-circle w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center mx-auto mb-2">
|
| 201 |
-
<i class="fas fa-check"></i>
|
| 202 |
-
</div>
|
| 203 |
-
<p class="text-sm font-medium text-gray-900">Confirmed</p>
|
| 204 |
-
</div>
|
| 205 |
-
</div>
|
| 206 |
-
<div class="flex-1 h-1 bg-gray-200 mt-[-20px]"></div>
|
| 207 |
-
<div class="flex-1 text-center">
|
| 208 |
-
<div class="progress-step">
|
| 209 |
-
<div class="step-circle w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center mx-auto mb-2">
|
| 210 |
-
<i class="fas fa-box"></i>
|
| 211 |
-
</div>
|
| 212 |
-
<p class="text-sm text-gray-500">Processing</p>
|
| 213 |
-
</div>
|
| 214 |
-
</div>
|
| 215 |
-
<div class="flex-1 h-1 bg-gray-200 mt-[-20px]"></div>
|
| 216 |
-
<div class="flex-1 text-center">
|
| 217 |
-
<div class="progress-step">
|
| 218 |
-
<div class="step-circle w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center mx-auto mb-2">
|
| 219 |
-
<i class="fas fa-truck"></i>
|
| 220 |
-
</div>
|
| 221 |
-
<p class="text-sm text-gray-500">Shipped</p>
|
| 222 |
-
</div>
|
| 223 |
-
</div>
|
| 224 |
-
<div class="flex-1 h-1 bg-gray-200 mt-[-20px]"></div>
|
| 225 |
-
<div class="flex-1 text-center">
|
| 226 |
-
<div class="progress-step">
|
| 227 |
-
<div class="step-circle w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center mx-auto mb-2">
|
| 228 |
-
<i class="fas fa-home"></i>
|
| 229 |
-
</div>
|
| 230 |
-
<p class="text-sm text-gray-500">Delivered</p>
|
| 231 |
-
</div>
|
| 232 |
-
</div>
|
| 233 |
-
</div>
|
| 234 |
-
</div>
|
| 235 |
-
|
| 236 |
-
<!-- Order Items -->
|
| 237 |
-
<div class="mb-6">
|
| 238 |
-
<h3 class="font-semibold text-gray-900 mb-4">Order Items</h3>
|
| 239 |
-
<div id="order-items" class="space-y-3">
|
| 240 |
-
<!-- Items loaded dynamically -->
|
| 241 |
-
</div>
|
| 242 |
-
</div>
|
| 243 |
-
|
| 244 |
-
<!-- Shipping Address -->
|
| 245 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
| 246 |
-
<div>
|
| 247 |
-
<h3 class="font-semibold text-gray-900 mb-3">Shipping Address</h3>
|
| 248 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 249 |
-
<p class="text-gray-600 text-sm" id="shipping-address">Loading...</p>
|
| 250 |
-
</div>
|
| 251 |
-
</div>
|
| 252 |
-
<div>
|
| 253 |
-
<h3 class="font-semibold text-gray-900 mb-3">Payment Method</h3>
|
| 254 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 255 |
-
<p class="text-gray-600 text-sm" id="payment-method">Loading...</p>
|
| 256 |
-
</div>
|
| 257 |
-
</div>
|
| 258 |
-
</div>
|
| 259 |
-
|
| 260 |
-
<!-- Order Summary -->
|
| 261 |
-
<div class="border-t pt-6">
|
| 262 |
-
<div class="space-y-2">
|
| 263 |
-
<div class="flex justify-between text-gray-600">
|
| 264 |
-
<span>Subtotal</span>
|
| 265 |
-
<span id="subtotal">$0.00</span>
|
| 266 |
-
</div>
|
| 267 |
-
<div class="flex justify-between text-gray-600">
|
| 268 |
-
<span>Shipping</span>
|
| 269 |
-
<span id="shipping">$0.00</span>
|
| 270 |
-
</div>
|
| 271 |
-
<div class="flex justify-between text-gray-600">
|
| 272 |
-
<span>Tax</span>
|
| 273 |
-
<span id="tax">$0.00</span>
|
| 274 |
-
</div>
|
| 275 |
-
<div class="flex justify-between text-gray-600" id="cod-fee-row" style="display: none;">
|
| 276 |
-
<span>COD Fee</span>
|
| 277 |
-
<span id="cod-fee">$5.00</span>
|
| 278 |
-
</div>
|
| 279 |
-
<div class="flex justify-between text-lg font-bold text-gray-900 pt-2 border-t">
|
| 280 |
-
<span>Total</span>
|
| 281 |
-
<span class="text-brand-500" id="total">$0.00</span>
|
| 282 |
-
</div>
|
| 283 |
-
</div>
|
| 284 |
-
</div>
|
| 285 |
-
</div>
|
| 286 |
-
</div>
|
| 287 |
-
|
| 288 |
-
<!-- Action Buttons -->
|
| 289 |
-
<div class="flex flex-col sm:flex-row gap-4 fade-in-up" style="animation-delay: 0.4s">
|
| 290 |
-
<a href="../index.html" class="flex-1 bg-white border-2 border-gray-200 text-gray-700 py-4 rounded-xl font-semibold text-center hover:bg-gray-50 transition-colors">
|
| 291 |
-
<i class="fas fa-shopping-bag mr-2"></i>
|
| 292 |
-
Continue Shopping
|
| 293 |
-
</a>
|
| 294 |
-
<a href="orders.html" class="flex-1 bg-brand-500 text-white py-4 rounded-xl font-semibold text-center hover:bg-brand-600 transition-colors">
|
| 295 |
-
<i class="fas fa-list mr-2"></i>
|
| 296 |
-
View All Orders
|
| 297 |
-
</a>
|
| 298 |
-
</div>
|
| 299 |
-
|
| 300 |
-
<!-- Help Section -->
|
| 301 |
-
<div class="mt-8 text-center fade-in-up" style="animation-delay: 0.6s">
|
| 302 |
-
<p class="text-gray-500 text-sm">
|
| 303 |
-
Need help? <a href="#" class="text-brand-500 hover:underline">Contact Support</a> or call us at <a href="tel:+1234567890" class="text-brand-500 hover:underline">+1 234 567 890</a>
|
| 304 |
-
</p>
|
| 305 |
-
</div>
|
| 306 |
-
</main>
|
| 307 |
-
</div>
|
| 308 |
-
|
| 309 |
-
<script>
|
| 310 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 311 |
-
// Auth check — redirect to login if not logged in
|
| 312 |
-
const { success: sessOk, user } = await window.EdgeFunctions.auth.checkSession();
|
| 313 |
-
if (!sessOk || !user) {
|
| 314 |
-
window.location.href = 'login.html?redirect=order-confirmation' + window.location.search;
|
| 315 |
-
return;
|
| 316 |
-
}
|
| 317 |
-
|
| 318 |
-
// Accept ?orderId=, ?order_id=, or legacy ?id=
|
| 319 |
-
const params = new URLSearchParams(window.location.search);
|
| 320 |
-
const orderId = params.get('orderId') || params.get('order_id') || params.get('id');
|
| 321 |
-
|
| 322 |
-
if (!orderId) {
|
| 323 |
-
window.location.href = '../index.html';
|
| 324 |
-
return;
|
| 325 |
-
}
|
| 326 |
-
|
| 327 |
-
try {
|
| 328 |
-
// Load order details via edge function
|
| 329 |
-
const result = await window.EdgeFunctions.orders.details(orderId);
|
| 330 |
-
|
| 331 |
-
if (!result.success || !result.order) {
|
| 332 |
-
showError(result.error || 'Order not found');
|
| 333 |
-
return;
|
| 334 |
-
}
|
| 335 |
-
|
| 336 |
-
// Render order details
|
| 337 |
-
renderOrderDetails(result.order);
|
| 338 |
-
|
| 339 |
-
// Create confetti
|
| 340 |
-
createConfetti();
|
| 341 |
-
|
| 342 |
-
// Show content
|
| 343 |
-
document.getElementById('loading').classList.add('hidden');
|
| 344 |
-
document.getElementById('content').classList.remove('hidden');
|
| 345 |
-
|
| 346 |
-
} catch (error) {
|
| 347 |
-
console.error('Error loading order:', error);
|
| 348 |
-
document.getElementById('loading').innerHTML = `
|
| 349 |
-
<div class="text-center">
|
| 350 |
-
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
| 351 |
-
<i class="fas fa-exclamation text-red-500 text-2xl"></i>
|
| 352 |
-
</div>
|
| 353 |
-
<h2 class="text-xl font-bold text-gray-900 mb-2">Order Not Found</h2>
|
| 354 |
-
<p class="text-gray-500 mb-4">We couldn't find your order details.</p>
|
| 355 |
-
<a href="../index.html" class="text-brand-500 hover:underline">Go to Homepage</a>
|
| 356 |
-
</div>
|
| 357 |
-
`;
|
| 358 |
-
}
|
| 359 |
-
});
|
| 360 |
-
|
| 361 |
-
function renderOrderDetails(order) {
|
| 362 |
-
// Order number and date
|
| 363 |
-
document.getElementById('order-number').textContent = `# ${order.id.substring(0, 8).toUpperCase()}`;
|
| 364 |
-
document.getElementById('order-date').textContent = new Date(order.created_at).toLocaleDateString('en-US', {
|
| 365 |
-
weekday: 'long',
|
| 366 |
-
year: 'numeric',
|
| 367 |
-
month: 'long',
|
| 368 |
-
day: 'numeric'
|
| 369 |
-
});
|
| 370 |
-
|
| 371 |
-
// Order items
|
| 372 |
-
const itemsContainer = document.getElementById('order-items');
|
| 373 |
-
if (order.items && order.items.length > 0) {
|
| 374 |
-
itemsContainer.innerHTML = order.items.map(item => `
|
| 375 |
-
<div class="flex items-center gap-4 bg-gray-50 rounded-xl p-3">
|
| 376 |
-
<img src="${item.image || 'https://via.placeholder.com/60'}" alt="${item.name}" class="w-14 h-14 rounded-lg object-cover">
|
| 377 |
-
<div class="flex-1">
|
| 378 |
-
<p class="font-medium text-gray-900">${item.name}</p>
|
| 379 |
-
<p class="text-sm text-gray-500">Qty: ${item.quantity}</p>
|
| 380 |
-
</div>
|
| 381 |
-
<p class="font-semibold text-gray-900">$${(item.price * item.quantity).toFixed(2)}</p>
|
| 382 |
-
</div>
|
| 383 |
-
`).join('');
|
| 384 |
-
}
|
| 385 |
-
|
| 386 |
-
// Shipping address
|
| 387 |
-
const addr = order.shipping_address || {};
|
| 388 |
-
document.getElementById('shipping-address').innerHTML = `
|
| 389 |
-
<p class="font-medium text-gray-900">${addr.fullname || 'N/A'}</p>
|
| 390 |
-
<p>${addr.address || ''}</p>
|
| 391 |
-
<p>${addr.city || ''}, ${addr.state || ''} ${addr.zipcode || ''}</p>
|
| 392 |
-
<p class="mt-2"><i class="fas fa-phone mr-2"></i>${addr.phone || 'N/A'}</p>
|
| 393 |
-
<p><i class="fas fa-envelope mr-2"></i>${addr.email || 'N/A'}</p>
|
| 394 |
-
`;
|
| 395 |
-
|
| 396 |
-
// Payment method
|
| 397 |
-
let paymentText = '';
|
| 398 |
-
if (order.payment_method === 'eeshapay') {
|
| 399 |
-
paymentText = '<p class="font-medium text-brand-500"><i class="fas fa-wallet mr-2"></i>EeshaPay</p>';
|
| 400 |
-
} else if (order.payment_method === 'card') {
|
| 401 |
-
paymentText = '<p class="font-medium"><i class="fas fa-credit-card mr-2"></i>Credit/Debit Card</p>';
|
| 402 |
-
} else if (order.payment_method === 'cod') {
|
| 403 |
-
paymentText = '<p class="font-medium text-green-600"><i class="fas fa-money-bill mr-2"></i>Cash on Delivery</p>';
|
| 404 |
-
document.getElementById('cod-fee-row').style.display = 'flex';
|
| 405 |
-
}
|
| 406 |
-
document.getElementById('payment-method').innerHTML = paymentText;
|
| 407 |
-
|
| 408 |
-
// Totals
|
| 409 |
-
document.getElementById('subtotal').textContent = `$${(order.subtotal || 0).toFixed(2)}`;
|
| 410 |
-
document.getElementById('shipping').textContent = order.shipping > 0 ? `$${order.shipping.toFixed(2)}` : 'Free';
|
| 411 |
-
document.getElementById('tax').textContent = `$${(order.tax || 0).toFixed(2)}`;
|
| 412 |
-
document.getElementById('total').textContent = `$${(order.total || 0).toFixed(2)}`;
|
| 413 |
-
}
|
| 414 |
-
|
| 415 |
-
function createConfetti() {
|
| 416 |
-
const colors = ['#f59e0b', '#22c55e', '#3b82f6', '#fbbf24', '#a855f7'];
|
| 417 |
-
|
| 418 |
-
for (let i = 0; i < 50; i++) {
|
| 419 |
-
setTimeout(() => {
|
| 420 |
-
const confetti = document.createElement('div');
|
| 421 |
-
confetti.className = 'confetti';
|
| 422 |
-
confetti.style.left = Math.random() * 100 + 'vw';
|
| 423 |
-
confetti.style.top = '-10px';
|
| 424 |
-
confetti.style.width = Math.random() * 10 + 5 + 'px';
|
| 425 |
-
confetti.style.height = confetti.style.width;
|
| 426 |
-
confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
|
| 427 |
-
confetti.style.borderRadius = Math.random() > 0.5 ? '50%' : '0';
|
| 428 |
-
confetti.style.animation = `confetti ${Math.random() * 2 + 2}s linear forwards`;
|
| 429 |
-
|
| 430 |
-
document.body.appendChild(confetti);
|
| 431 |
-
|
| 432 |
-
confetti.addEventListener('animationend', () => confetti.remove());
|
| 433 |
-
}, i * 50);
|
| 434 |
-
}
|
| 435 |
-
}
|
| 436 |
-
|
| 437 |
-
function showError(message) {
|
| 438 |
-
document.getElementById('loading').innerHTML = `
|
| 439 |
-
<div class="text-center">
|
| 440 |
-
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
| 441 |
-
<i class="fas fa-exclamation text-red-500 text-2xl"></i>
|
| 442 |
-
</div>
|
| 443 |
-
<h2 class="text-xl font-bold text-gray-900 mb-2">Error</h2>
|
| 444 |
-
<p class="text-gray-500">${message}</p>
|
| 445 |
-
</div>
|
| 446 |
-
`;
|
| 447 |
-
}
|
| 448 |
-
</script>
|
| 449 |
-
</body>
|
| 450 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/orders.html
DELETED
|
@@ -1,422 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>My Orders - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* { font-family: 'Plus Jakarta Sans', system-ui, sans-serif; }
|
| 44 |
-
.loading { animation: spin 1s linear infinite; }
|
| 45 |
-
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 46 |
-
</style>
|
| 47 |
-
</head>
|
| 48 |
-
<body class="bg-gray-100 min-h-screen">
|
| 49 |
-
<!-- Header -->
|
| 50 |
-
<header class="bg-white shadow-md sticky top-0 z-50">
|
| 51 |
-
<nav class="container mx-auto px-4 py-4 flex justify-between items-center">
|
| 52 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 53 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 54 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 55 |
-
<defs>
|
| 56 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 57 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 58 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 59 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 60 |
-
</linearGradient>
|
| 61 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 62 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 63 |
-
<feMerge>
|
| 64 |
-
<feMergeNode in="coloredBlur"/>
|
| 65 |
-
<feMergeNode in="SourceGraphic"/>
|
| 66 |
-
</feMerge>
|
| 67 |
-
</filter>
|
| 68 |
-
</defs>
|
| 69 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 70 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 71 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 72 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 73 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 74 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 75 |
-
<g transform="translate(52, 8)">
|
| 76 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 77 |
-
</g>
|
| 78 |
-
</svg>
|
| 79 |
-
</div>
|
| 80 |
-
<div class="flex flex-col">
|
| 81 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 82 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 83 |
-
</div>
|
| 84 |
-
</a>
|
| 85 |
-
<div class="flex items-center gap-4">
|
| 86 |
-
<a href="../index.html" class="text-gray-600 hover:text-brand-500">
|
| 87 |
-
<i class="fas fa-home text-xl"></i>
|
| 88 |
-
</a>
|
| 89 |
-
<a href="cart.html" class="text-gray-600 hover:text-brand-500 relative">
|
| 90 |
-
<i class="fas fa-shopping-cart text-xl"></i>
|
| 91 |
-
<span id="cartCount" class="absolute -top-2 -right-2 bg-brand-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 92 |
-
</a>
|
| 93 |
-
</div>
|
| 94 |
-
</nav>
|
| 95 |
-
</header>
|
| 96 |
-
|
| 97 |
-
<!-- Main Content -->
|
| 98 |
-
<main class="container mx-auto px-4 py-8">
|
| 99 |
-
<!-- Back Link -->
|
| 100 |
-
<a href="../index.html" class="inline-flex items-center text-gray-600 hover:text-brand-500 mb-6">
|
| 101 |
-
<i class="fas fa-arrow-left mr-2"></i> Back to Home
|
| 102 |
-
</a>
|
| 103 |
-
|
| 104 |
-
<div class="max-w-6xl mx-auto">
|
| 105 |
-
<!-- Page Title -->
|
| 106 |
-
<div class="flex justify-between items-center mb-6">
|
| 107 |
-
<h1 class="text-3xl font-bold text-gray-900">My Orders</h1>
|
| 108 |
-
<div class="flex gap-2">
|
| 109 |
-
<select id="filterStatus" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary">
|
| 110 |
-
<option value="all">All Orders</option>
|
| 111 |
-
<option value="pending">Pending</option>
|
| 112 |
-
<option value="processing">Processing</option>
|
| 113 |
-
<option value="shipped">Shipped</option>
|
| 114 |
-
<option value="delivered">Delivered</option>
|
| 115 |
-
<option value="cancelled">Cancelled</option>
|
| 116 |
-
</select>
|
| 117 |
-
</div>
|
| 118 |
-
</div>
|
| 119 |
-
|
| 120 |
-
<!-- Order Stats -->
|
| 121 |
-
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
|
| 122 |
-
<div class="bg-white p-4 rounded-lg shadow text-center">
|
| 123 |
-
<div class="text-2xl font-bold text-yellow-500" id="pendingCount">0</div>
|
| 124 |
-
<div class="text-sm text-gray-500">Pending</div>
|
| 125 |
-
</div>
|
| 126 |
-
<div class="bg-white p-4 rounded-lg shadow text-center">
|
| 127 |
-
<div class="text-2xl font-bold text-blue-500" id="processingCount">0</div>
|
| 128 |
-
<div class="text-sm text-gray-500">Processing</div>
|
| 129 |
-
</div>
|
| 130 |
-
<div class="bg-white p-4 rounded-lg shadow text-center">
|
| 131 |
-
<div class="text-2xl font-bold text-purple-500" id="shippedCount">0</div>
|
| 132 |
-
<div class="text-sm text-gray-500">Shipped</div>
|
| 133 |
-
</div>
|
| 134 |
-
<div class="bg-white p-4 rounded-lg shadow text-center">
|
| 135 |
-
<div class="text-2xl font-bold text-green-500" id="deliveredCount">0</div>
|
| 136 |
-
<div class="text-sm text-gray-500">Delivered</div>
|
| 137 |
-
</div>
|
| 138 |
-
<div class="bg-white p-4 rounded-lg shadow text-center">
|
| 139 |
-
<div class="text-2xl font-bold text-red-500" id="cancelledCount">0</div>
|
| 140 |
-
<div class="text-sm text-gray-500">Cancelled</div>
|
| 141 |
-
</div>
|
| 142 |
-
</div>
|
| 143 |
-
|
| 144 |
-
<!-- Orders List -->
|
| 145 |
-
<div id="ordersList" class="space-y-4">
|
| 146 |
-
<!-- Loading state -->
|
| 147 |
-
<div class="bg-white rounded-lg shadow p-8 text-center">
|
| 148 |
-
<i class="fas fa-spinner loading text-4xl text-brand-500 mb-4"></i>
|
| 149 |
-
<p class="text-gray-500">Loading your orders...</p>
|
| 150 |
-
</div>
|
| 151 |
-
</div>
|
| 152 |
-
</div>
|
| 153 |
-
</main>
|
| 154 |
-
|
| 155 |
-
<!-- Order Detail Modal -->
|
| 156 |
-
<div id="orderModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center p-4">
|
| 157 |
-
<div class="bg-white rounded-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
| 158 |
-
<div class="p-6 border-b flex justify-between items-center">
|
| 159 |
-
<h2 class="text-xl font-bold">Order Details</h2>
|
| 160 |
-
<button onclick="closeOrderModal()" class="text-gray-500 hover:text-gray-700">
|
| 161 |
-
<i class="fas fa-times text-xl"></i>
|
| 162 |
-
</button>
|
| 163 |
-
</div>
|
| 164 |
-
<div id="orderModalContent" class="p-6">
|
| 165 |
-
<!-- Content will be inserted here -->
|
| 166 |
-
</div>
|
| 167 |
-
</div>
|
| 168 |
-
</div>
|
| 169 |
-
|
| 170 |
-
<!-- Footer -->
|
| 171 |
-
<footer class="bg-dark-900 text-white py-8 mt-12">
|
| 172 |
-
<div class="container mx-auto px-4 text-center">
|
| 173 |
-
<p>© 2025 Cellex. All rights reserved.</p>
|
| 174 |
-
</div>
|
| 175 |
-
</footer>
|
| 176 |
-
|
| 177 |
-
<script>
|
| 178 |
-
let currentUser = null;
|
| 179 |
-
let allOrders = [];
|
| 180 |
-
|
| 181 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 182 |
-
await checkAuth();
|
| 183 |
-
await loadOrders();
|
| 184 |
-
await updateCartCount();
|
| 185 |
-
});
|
| 186 |
-
|
| 187 |
-
async function checkAuth() {
|
| 188 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 189 |
-
if (!user) {
|
| 190 |
-
window.location.href = 'login.html?redirect=orders';
|
| 191 |
-
return;
|
| 192 |
-
}
|
| 193 |
-
currentUser = user;
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
async function loadOrders() {
|
| 197 |
-
if (!currentUser) return;
|
| 198 |
-
|
| 199 |
-
const result = await window.EdgeFunctions.orders.list();
|
| 200 |
-
|
| 201 |
-
if (!result.success) {
|
| 202 |
-
console.error('Error loading orders:', result.error);
|
| 203 |
-
document.getElementById('ordersList').innerHTML = `
|
| 204 |
-
<div class="bg-white rounded-lg shadow p-8 text-center text-red-500">
|
| 205 |
-
<i class="fas fa-exclamation-circle text-4xl mb-4"></i>
|
| 206 |
-
<p>Failed to load orders. Please try again.</p>
|
| 207 |
-
</div>
|
| 208 |
-
`;
|
| 209 |
-
return;
|
| 210 |
-
}
|
| 211 |
-
|
| 212 |
-
allOrders = result.orders || [];
|
| 213 |
-
updateStats();
|
| 214 |
-
renderOrders(allOrders);
|
| 215 |
-
|
| 216 |
-
// Set up filter
|
| 217 |
-
document.getElementById('filterStatus').addEventListener('change', (e) => {
|
| 218 |
-
const status = e.target.value;
|
| 219 |
-
const filtered = status === 'all' ? allOrders : allOrders.filter(o => o.status?.toLowerCase() === status);
|
| 220 |
-
renderOrders(filtered);
|
| 221 |
-
});
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
-
function updateStats() {
|
| 225 |
-
const stats = {
|
| 226 |
-
pending: 0,
|
| 227 |
-
processing: 0,
|
| 228 |
-
shipped: 0,
|
| 229 |
-
delivered: 0,
|
| 230 |
-
cancelled: 0
|
| 231 |
-
};
|
| 232 |
-
|
| 233 |
-
allOrders.forEach(order => {
|
| 234 |
-
const status = (order.status || 'pending').toLowerCase();
|
| 235 |
-
if (stats.hasOwnProperty(status)) {
|
| 236 |
-
stats[status]++;
|
| 237 |
-
}
|
| 238 |
-
});
|
| 239 |
-
|
| 240 |
-
Object.keys(stats).forEach(key => {
|
| 241 |
-
const el = document.getElementById(key + 'Count');
|
| 242 |
-
if (el) el.textContent = stats[key];
|
| 243 |
-
});
|
| 244 |
-
}
|
| 245 |
-
|
| 246 |
-
function renderOrders(orders) {
|
| 247 |
-
const container = document.getElementById('ordersList');
|
| 248 |
-
|
| 249 |
-
if (orders.length === 0) {
|
| 250 |
-
container.innerHTML = `
|
| 251 |
-
<div class="bg-white rounded-lg shadow p-8 text-center">
|
| 252 |
-
<i class="fas fa-box-open text-6xl text-gray-300 mb-4"></i>
|
| 253 |
-
<h3 class="text-xl font-semibold text-gray-700 mb-2">No orders found</h3>
|
| 254 |
-
<p class="text-gray-500 mb-6">You haven't placed any orders yet.</p>
|
| 255 |
-
<a href="../index.html" class="inline-block bg-brand-500 text-white px-6 py-2 rounded-lg hover:bg-brand-600">
|
| 256 |
-
Start Shopping
|
| 257 |
-
</a>
|
| 258 |
-
</div>
|
| 259 |
-
`;
|
| 260 |
-
return;
|
| 261 |
-
}
|
| 262 |
-
|
| 263 |
-
container.innerHTML = orders.map(order => {
|
| 264 |
-
const statusClass = getStatusClass(order.status);
|
| 265 |
-
const items = order.buyers_order_items || [];
|
| 266 |
-
const itemPreview = items.slice(0, 3);
|
| 267 |
-
const moreItems = items.length - 3;
|
| 268 |
-
|
| 269 |
-
return `
|
| 270 |
-
<div class="bg-white rounded-lg shadow hover:shadow-lg transition cursor-pointer" onclick="viewOrder('${order.id}')">
|
| 271 |
-
<div class="p-6">
|
| 272 |
-
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
| 273 |
-
<div class="flex items-center gap-4">
|
| 274 |
-
<div class="flex -space-x-2">
|
| 275 |
-
${itemPreview.map(item => `
|
| 276 |
-
<img src="${item.product_image || item.products?.image_url || item.products?.image || 'https://via.placeholder.com/50'}"
|
| 277 |
-
alt="${item.product_name || item.products?.name || 'Product'}"
|
| 278 |
-
class="w-12 h-12 rounded-lg border-2 border-white object-cover">
|
| 279 |
-
`).join('')}
|
| 280 |
-
${moreItems > 0 ? `
|
| 281 |
-
<div class="w-12 h-12 rounded-lg bg-gray-200 flex items-center justify-center text-sm font-medium text-gray-600">
|
| 282 |
-
+${moreItems}
|
| 283 |
-
</div>
|
| 284 |
-
` : ''}
|
| 285 |
-
</div>
|
| 286 |
-
<div>
|
| 287 |
-
<h3 class="font-semibold">Order #${order.id.slice(0, 8).toUpperCase()}</h3>
|
| 288 |
-
<p class="text-sm text-gray-500">${items.length} item${items.length !== 1 ? 's' : ''}</p>
|
| 289 |
-
</div>
|
| 290 |
-
</div>
|
| 291 |
-
<div class="text-right">
|
| 292 |
-
<p class="font-bold text-lg">$${(order.total || 0).toFixed(2)}</p>
|
| 293 |
-
<p class="text-sm text-gray-500">${new Date(order.created_at).toLocaleDateString()}</p>
|
| 294 |
-
</div>
|
| 295 |
-
</div>
|
| 296 |
-
<div class="mt-4 flex items-center justify-between">
|
| 297 |
-
<span class="px-3 py-1 rounded-full text-sm font-medium ${statusClass}">
|
| 298 |
-
${order.status || 'Pending'}
|
| 299 |
-
</span>
|
| 300 |
-
<span class="text-brand-500 text-sm font-medium">
|
| 301 |
-
View Details <i class="fas fa-chevron-right ml-1"></i>
|
| 302 |
-
</span>
|
| 303 |
-
</div>
|
| 304 |
-
</div>
|
| 305 |
-
</div>
|
| 306 |
-
`;
|
| 307 |
-
}).join('');
|
| 308 |
-
}
|
| 309 |
-
|
| 310 |
-
function getStatusClass(status) {
|
| 311 |
-
const classes = {
|
| 312 |
-
pending: 'bg-yellow-100 text-yellow-800',
|
| 313 |
-
processing: 'bg-blue-100 text-blue-800',
|
| 314 |
-
shipped: 'bg-purple-100 text-purple-800',
|
| 315 |
-
delivered: 'bg-green-100 text-green-800',
|
| 316 |
-
cancelled: 'bg-red-100 text-red-800'
|
| 317 |
-
};
|
| 318 |
-
return classes[(status || 'pending').toLowerCase()] || 'bg-gray-100 text-gray-800';
|
| 319 |
-
}
|
| 320 |
-
|
| 321 |
-
async function viewOrder(orderId) {
|
| 322 |
-
const order = allOrders.find(o => o.id === orderId);
|
| 323 |
-
if (!order) return;
|
| 324 |
-
|
| 325 |
-
const items = order.buyers_order_items || [];
|
| 326 |
-
const statusClass = getStatusClass(order.status);
|
| 327 |
-
|
| 328 |
-
document.getElementById('orderModalContent').innerHTML = `
|
| 329 |
-
<div class="space-y-6">
|
| 330 |
-
<div class="flex justify-between items-start">
|
| 331 |
-
<div>
|
| 332 |
-
<p class="text-sm text-gray-500">Order ID</p>
|
| 333 |
-
<p class="font-mono font-bold">#${order.id.slice(0, 8).toUpperCase()}</p>
|
| 334 |
-
</div>
|
| 335 |
-
<span class="px-3 py-1 rounded-full text-sm font-medium ${statusClass}">
|
| 336 |
-
${order.status || 'Pending'}
|
| 337 |
-
</span>
|
| 338 |
-
</div>
|
| 339 |
-
|
| 340 |
-
<div class="border-t pt-4">
|
| 341 |
-
<h4 class="font-semibold mb-3">Order Items</h4>
|
| 342 |
-
<div class="space-y-3">
|
| 343 |
-
${items.map(item => `
|
| 344 |
-
<div class="flex items-center gap-3">
|
| 345 |
-
<img src="${item.product_image || item.products?.image_url || item.products?.image || 'https://via.placeholder.com/60'}"
|
| 346 |
-
alt="${item.product_name || item.products?.name || 'Product'}"
|
| 347 |
-
class="w-16 h-16 rounded-lg object-cover">
|
| 348 |
-
<div class="flex-1">
|
| 349 |
-
<p class="font-medium">${item.product_name || item.products?.name || 'Unknown Product'}</p>
|
| 350 |
-
<p class="text-sm text-gray-500">Qty: ${item.quantity}</p>
|
| 351 |
-
</div>
|
| 352 |
-
<p class="font-semibold">$${(item.price * item.quantity).toFixed(2)}</p>
|
| 353 |
-
</div>
|
| 354 |
-
`).join('')}
|
| 355 |
-
</div>
|
| 356 |
-
</div>
|
| 357 |
-
|
| 358 |
-
<div class="border-t pt-4 space-y-2">
|
| 359 |
-
<div class="flex justify-between">
|
| 360 |
-
<span class="text-gray-500">Subtotal</span>
|
| 361 |
-
<span>$${(order.subtotal || order.total || 0).toFixed(2)}</span>
|
| 362 |
-
</div>
|
| 363 |
-
<div class="flex justify-between">
|
| 364 |
-
<span class="text-gray-500">Shipping</span>
|
| 365 |
-
<span>$${(order.shipping || 0).toFixed(2)}</span>
|
| 366 |
-
</div>
|
| 367 |
-
<div class="flex justify-between">
|
| 368 |
-
<span class="text-gray-500">Tax</span>
|
| 369 |
-
<span>$${(order.tax || 0).toFixed(2)}</span>
|
| 370 |
-
</div>
|
| 371 |
-
<div class="flex justify-between font-bold text-lg pt-2 border-t">
|
| 372 |
-
<span>Total</span>
|
| 373 |
-
<span>$${(order.total || 0).toFixed(2)}</span>
|
| 374 |
-
</div>
|
| 375 |
-
</div>
|
| 376 |
-
|
| 377 |
-
<div class="border-t pt-4">
|
| 378 |
-
<h4 class="font-semibold mb-2">Order Date</h4>
|
| 379 |
-
<p class="text-gray-600">${new Date(order.created_at).toLocaleString()}</p>
|
| 380 |
-
</div>
|
| 381 |
-
|
| 382 |
-
${order.tracking_number ? `
|
| 383 |
-
<div class="border-t pt-4">
|
| 384 |
-
<h4 class="font-semibold mb-2">Tracking Number</h4>
|
| 385 |
-
<p class="font-mono text-brand-500">${order.tracking_number}</p>
|
| 386 |
-
</div>
|
| 387 |
-
` : ''}
|
| 388 |
-
|
| 389 |
-
<div class="flex gap-3 pt-4">
|
| 390 |
-
<button onclick="closeOrderModal()" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">
|
| 391 |
-
Close
|
| 392 |
-
</button>
|
| 393 |
-
${order.status === 'delivered' ? `
|
| 394 |
-
<button class="flex-1 px-4 py-2 bg-brand-500 text-white rounded-lg hover:bg-brand-600">
|
| 395 |
-
Write Review
|
| 396 |
-
</button>
|
| 397 |
-
` : ''}
|
| 398 |
-
</div>
|
| 399 |
-
</div>
|
| 400 |
-
`;
|
| 401 |
-
|
| 402 |
-
document.getElementById('orderModal').classList.remove('hidden');
|
| 403 |
-
}
|
| 404 |
-
|
| 405 |
-
function closeOrderModal() {
|
| 406 |
-
document.getElementById('orderModal').classList.add('hidden');
|
| 407 |
-
}
|
| 408 |
-
|
| 409 |
-
async function updateCartCount() {
|
| 410 |
-
const count = await window.Cart.getCartCount();
|
| 411 |
-
|
| 412 |
-
const el = document.getElementById('cartCount');
|
| 413 |
-
if (el) el.textContent = count;
|
| 414 |
-
}
|
| 415 |
-
|
| 416 |
-
// Close modal on outside click
|
| 417 |
-
document.getElementById('orderModal').addEventListener('click', (e) => {
|
| 418 |
-
if (e.target.id === 'orderModal') closeOrderModal();
|
| 419 |
-
});
|
| 420 |
-
</script>
|
| 421 |
-
</body>
|
| 422 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/product.html
DELETED
|
@@ -1,355 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Product Details - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 8 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 9 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* { font-family: 'Plus Jakarta Sans', system-ui, sans-serif; }
|
| 44 |
-
body {
|
| 45 |
-
background-color: #f5f5f5;
|
| 46 |
-
}
|
| 47 |
-
</style>
|
| 48 |
-
</head>
|
| 49 |
-
<body>
|
| 50 |
-
<!-- Header (personalized like index.html) -->
|
| 51 |
-
<header class="bg-white shadow-md fixed top-0 left-0 right-0 z-50">
|
| 52 |
-
<nav class="container mx-auto px-4 py-4">
|
| 53 |
-
<div class="flex items-center justify-between">
|
| 54 |
-
<!-- Logo -->
|
| 55 |
-
<div class="flex items-center">
|
| 56 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 57 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 58 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 59 |
-
<defs>
|
| 60 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 61 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 62 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 63 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 64 |
-
</linearGradient>
|
| 65 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 66 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 67 |
-
<feMerge>
|
| 68 |
-
<feMergeNode in="coloredBlur"/>
|
| 69 |
-
<feMergeNode in="SourceGraphic"/>
|
| 70 |
-
</feMerge>
|
| 71 |
-
</filter>
|
| 72 |
-
</defs>
|
| 73 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 74 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 75 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 76 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 77 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 78 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 79 |
-
<g transform="translate(52, 8)">
|
| 80 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 81 |
-
</g>
|
| 82 |
-
</svg>
|
| 83 |
-
</div>
|
| 84 |
-
<div class="flex flex-col">
|
| 85 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 86 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 87 |
-
</div>
|
| 88 |
-
</a>
|
| 89 |
-
</div>
|
| 90 |
-
|
| 91 |
-
<!-- Search Bar -->
|
| 92 |
-
<div class="hidden md:flex flex-1 max-w-xl mx-8">
|
| 93 |
-
<div class="relative w-full">
|
| 94 |
-
<input type="text" placeholder="Search products..." id="searchInput"
|
| 95 |
-
class="w-full px-4 py-2 border-2 border-gray-200 rounded-lg focus:outline-none focus:border-brand-500">
|
| 96 |
-
<button class="absolute right-0 top-0 bottom-0 px-4 bg-brand-500 text-white rounded-r-lg" aria-label="Search">
|
| 97 |
-
<i class="fas fa-search"></i>
|
| 98 |
-
</button>
|
| 99 |
-
</div>
|
| 100 |
-
</div>
|
| 101 |
-
|
| 102 |
-
<!-- Navigation Links / Auth -->
|
| 103 |
-
<div class="flex items-center space-x-6" id="auth-links">
|
| 104 |
-
<a href="login.html" class="hidden md:block text-gray-600 hover:text-brand-500" id="login-link">
|
| 105 |
-
<i class="fas fa-user mr-2"></i>Login
|
| 106 |
-
</a>
|
| 107 |
-
<a href="signup.html" class="hidden md:block text-gray-600 hover:text-brand-500" id="signup-link">
|
| 108 |
-
<i class="fas fa-user-plus mr-2"></i>Sign Up
|
| 109 |
-
</a>
|
| 110 |
-
<div id="user-menu" class="hidden relative">
|
| 111 |
-
<button id="user-menu-button" class="flex items-center space-x-2 text-gray-600 hover:text-brand-500" aria-label="Open user menu">
|
| 112 |
-
<i class="fas fa-user-circle text-xl"></i>
|
| 113 |
-
<span id="user-email" class="hidden md:block"></span>
|
| 114 |
-
</button>
|
| 115 |
-
<div id="user-menu-dropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1">
|
| 116 |
-
<a href="#" id="logout-button" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Logout</a>
|
| 117 |
-
</div>
|
| 118 |
-
</div>
|
| 119 |
-
<a href="cart.html" class="relative text-gray-600 hover:text-brand-500">
|
| 120 |
-
<i class="fas fa-shopping-cart text-xl"></i>
|
| 121 |
-
<span id="cartCount" class="absolute -top-2 -right-2 bg-brand-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 122 |
-
</a>
|
| 123 |
-
<button class="md:hidden text-gray-600" id="mobileMenuBtn" aria-label="Toggle mobile menu">
|
| 124 |
-
<i class="fas fa-bars text-xl"></i>
|
| 125 |
-
</button>
|
| 126 |
-
</div>
|
| 127 |
-
</div>
|
| 128 |
-
|
| 129 |
-
<!-- Mobile Menu -->
|
| 130 |
-
<div class="md:hidden hidden" id="mobileMenu">
|
| 131 |
-
<div class="px-4 py-2 bg-custom-red-50">
|
| 132 |
-
<div class="flex flex-col space-y-4" id="mobile-auth-links">
|
| 133 |
-
<a href="login.html" class="text-gray-600">Login</a>
|
| 134 |
-
<a href="signup.html" class="text-gray-600">Sign Up</a>
|
| 135 |
-
<a href="#" id="mobile-logout-button" class="text-gray-600 hidden">Logout</a>
|
| 136 |
-
<div class="border-t border-gray-200 pt-2">
|
| 137 |
-
<a href="#" class="block py-2 text-gray-600">Electronics</a>
|
| 138 |
-
<a href="#" class="block py-2 text-gray-600">Fashion</a>
|
| 139 |
-
<a href="#" class="block py-2 text-gray-600">Home & Living</a>
|
| 140 |
-
</div>
|
| 141 |
-
</div>
|
| 142 |
-
</div>
|
| 143 |
-
</div>
|
| 144 |
-
</nav>
|
| 145 |
-
</header>
|
| 146 |
-
|
| 147 |
-
<!-- Product Details -->
|
| 148 |
-
<main class="container mx-auto px-4 py-8">
|
| 149 |
-
<div class="bg-white rounded-xl shadow-lg p-6 grid grid-cols-1 lg:grid-cols-2 gap-8">
|
| 150 |
-
<!-- Loading Skeleton -->
|
| 151 |
-
<div id="loadingSkeleton" class="animate-pulse">
|
| 152 |
-
<div class="bg-gray-200 rounded-lg w-full aspect-square mb-4"></div>
|
| 153 |
-
<div class="grid grid-cols-5 gap-2">
|
| 154 |
-
<div class="bg-gray-200 rounded aspect-square"></div>
|
| 155 |
-
<div class="bg-gray-200 rounded aspect-square"></div>
|
| 156 |
-
<div class="bg-gray-200 rounded aspect-square"></div>
|
| 157 |
-
<div class="bg-gray-200 rounded aspect-square"></div>
|
| 158 |
-
<div class="bg-gray-200 rounded aspect-square"></div>
|
| 159 |
-
</div>
|
| 160 |
-
</div>
|
| 161 |
-
|
| 162 |
-
<!-- Product Images -->
|
| 163 |
-
<div id="productImagesContainer" class="sticky top-24 hidden">
|
| 164 |
-
<div class="rounded-lg overflow-hidden mb-4">
|
| 165 |
-
<img id="mainImage" src="" alt="Product" class="w-full h-auto object-cover">
|
| 166 |
-
</div>
|
| 167 |
-
<div id="thumbnailContainer" class="grid grid-cols-5 gap-2"></div>
|
| 168 |
-
</div>
|
| 169 |
-
|
| 170 |
-
<!-- Product Info -->
|
| 171 |
-
<div class="space-y-6">
|
| 172 |
-
<!-- Loading Text -->
|
| 173 |
-
<div id="loadingText" class="animate-pulse">
|
| 174 |
-
<div class="h-4 bg-gray-200 rounded w-1/4 mb-2"></div>
|
| 175 |
-
<div class="h-8 bg-gray-200 rounded w-3/4"></div>
|
| 176 |
-
</div>
|
| 177 |
-
|
| 178 |
-
<div id="productInfo" class="hidden">
|
| 179 |
-
<span id="productCategory" class="text-gray-500 text-sm"></span>
|
| 180 |
-
<h1 id="productName" class="text-2xl md:text-3xl font-bold mt-2"></h1>
|
| 181 |
-
</div>
|
| 182 |
-
|
| 183 |
-
<!-- Rating & Reviews -->
|
| 184 |
-
<div class="flex items-center gap-2" id="productRating">
|
| 185 |
-
<div class="text-yellow-400"></div>
|
| 186 |
-
<span class="text-gray-600"></span>
|
| 187 |
-
</div>
|
| 188 |
-
|
| 189 |
-
<!-- Price -->
|
| 190 |
-
<div id="priceSection" class="hidden">
|
| 191 |
-
<div class="flex items-baseline gap-4">
|
| 192 |
-
<span id="productPrice" class="text-3xl md:text-4xl font-bold text-brand-500"></span>
|
| 193 |
-
<span id="originalPrice" class="text-gray-400 line-through text-lg"></span>
|
| 194 |
-
<span id="discountBadge" class="bg-brand-500 text-white text-sm px-2 py-1 rounded"></span>
|
| 195 |
-
</div>
|
| 196 |
-
</div>
|
| 197 |
-
|
| 198 |
-
<!-- Description -->
|
| 199 |
-
<div id="descriptionSection" class="hidden">
|
| 200 |
-
<h3 class="font-semibold text-lg mb-2">Description</h3>
|
| 201 |
-
<p id="productDescription" class="text-gray-600 leading-relaxed"></p>
|
| 202 |
-
</div>
|
| 203 |
-
|
| 204 |
-
<!-- Quantity Selector -->
|
| 205 |
-
<div class="flex items-center gap-4">
|
| 206 |
-
<span class="font-semibold">Quantity:</span>
|
| 207 |
-
<div class="flex items-center border rounded-lg">
|
| 208 |
-
<button onclick="decreaseQty()" class="px-4 py-2 hover:bg-gray-100 transition" aria-label="Decrease quantity">-</button>
|
| 209 |
-
<span id="quantity" class="px-6 py-2 border-x font-semibold">1</span>
|
| 210 |
-
<button onclick="increaseQty()" class="px-4 py-2 hover:bg-gray-100 transition" aria-label="Increase quantity">+</button>
|
| 211 |
-
</div>
|
| 212 |
-
</div>
|
| 213 |
-
|
| 214 |
-
<!-- Action Buttons -->
|
| 215 |
-
<div class="flex gap-4">
|
| 216 |
-
<button onclick="addToCart()" class="flex-1 bg-brand-500 text-white py-3 rounded-lg hover:bg-opacity-90 transition flex items-center justify-center gap-2">
|
| 217 |
-
<i class="fas fa-shopping-cart"></i>
|
| 218 |
-
Add to Cart
|
| 219 |
-
</button>
|
| 220 |
-
<button onclick="toggleWishlist()" class="px-4 py-3 border-2 border-brand-500 text-brand-500 rounded-lg hover:bg-brand-500 hover:text-white transition" aria-label="Add to wishlist">
|
| 221 |
-
<i class="fas fa-heart"></i>
|
| 222 |
-
</button>
|
| 223 |
-
</div>
|
| 224 |
-
|
| 225 |
-
<!-- Share & Group Buy buttons (Phase 2) -->
|
| 226 |
-
<div class="flex gap-2 flex-wrap mt-3" id="phase2-actions">
|
| 227 |
-
<button onclick="shareProduct('whatsapp')" class="px-3 py-2 bg-green-500 text-white rounded-lg text-sm font-medium hover:bg-green-600 transition flex items-center gap-1.5">
|
| 228 |
-
<i class="fab fa-whatsapp"></i> Share on WhatsApp
|
| 229 |
-
</button>
|
| 230 |
-
<button onclick="shareProduct('telegram')" class="px-3 py-2 bg-blue-500 text-white rounded-lg text-sm font-medium hover:bg-blue-600 transition flex items-center gap-1.5">
|
| 231 |
-
<i class="fab fa-telegram"></i> Telegram
|
| 232 |
-
</button>
|
| 233 |
-
<button onclick="copyShareLink()" class="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition flex items-center gap-1.5">
|
| 234 |
-
<i class="fas fa-link"></i> Copy link
|
| 235 |
-
</button>
|
| 236 |
-
<button onclick="startGroupBuy()" class="px-3 py-2 bg-gradient-to-r from-amber-400 to-amber-600 text-ink-900 rounded-lg text-sm font-bold hover:opacity-90 transition flex items-center gap-1.5">
|
| 237 |
-
<i class="fas fa-users"></i> Start group buy · 20% off
|
| 238 |
-
</button>
|
| 239 |
-
</div>
|
| 240 |
-
|
| 241 |
-
<!-- Active group buys for this product -->
|
| 242 |
-
<div id="activeGroupBuys" class="mt-3 hidden"></div>
|
| 243 |
-
|
| 244 |
-
<!-- Shipping & Policy -->
|
| 245 |
-
<div class="border-t pt-6 space-y-4">
|
| 246 |
-
<div class="flex items-start gap-3">
|
| 247 |
-
<i class="fas fa-truck text-brand-500 mt-1"></i>
|
| 248 |
-
<div>
|
| 249 |
-
<h4 class="font-semibold">Free Shipping</h4>
|
| 250 |
-
<p class="text-sm text-gray-600">Estimated delivery: 3-5 business days</p>
|
| 251 |
-
</div>
|
| 252 |
-
</div>
|
| 253 |
-
<div class="flex items-start gap-3">
|
| 254 |
-
<i class="fas fa-shield-alt text-brand-500 mt-1"></i>
|
| 255 |
-
<div>
|
| 256 |
-
<h4 class="font-semibold">Secure Payment</h4>
|
| 257 |
-
<p class="text-sm text-gray-600">Safe & secure checkout</p>
|
| 258 |
-
</div>
|
| 259 |
-
</div>
|
| 260 |
-
</div>
|
| 261 |
-
</div>
|
| 262 |
-
</div>
|
| 263 |
-
|
| 264 |
-
<!-- Reviews & Ratings (Phase 2) -->
|
| 265 |
-
<section class="mt-8 bg-white rounded-xl shadow-lg p-6">
|
| 266 |
-
<div class="flex items-center justify-between mb-4">
|
| 267 |
-
<h2 class="text-xl font-bold">Ratings & Reviews</h2>
|
| 268 |
-
<button onclick="openReviewModal()" class="bg-brand-500 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-opacity-90 transition">
|
| 269 |
-
<i class="fas fa-pen mr-1"></i> Write a review
|
| 270 |
-
</button>
|
| 271 |
-
</div>
|
| 272 |
-
|
| 273 |
-
<div id="reviewsSummary" class="mb-4 hidden">
|
| 274 |
-
<div class="flex items-center gap-4">
|
| 275 |
-
<div class="text-center">
|
| 276 |
-
<div class="text-4xl font-bold text-amber-500" id="reviewsAvg">0.0</div>
|
| 277 |
-
<div class="text-yellow-400 text-sm" id="reviewsStars"></div>
|
| 278 |
-
<div class="text-gray-500 text-xs mt-1" id="reviewsCount">0 reviews</div>
|
| 279 |
-
</div>
|
| 280 |
-
<div class="text-sm text-gray-600 max-w-md" id="reviewsBlurb">
|
| 281 |
-
Be the first to review this product.
|
| 282 |
-
</div>
|
| 283 |
-
</div>
|
| 284 |
-
</div>
|
| 285 |
-
|
| 286 |
-
<div id="reviewsList" class="space-y-4">
|
| 287 |
-
<div class="text-gray-500 text-sm">Loading reviews…</div>
|
| 288 |
-
</div>
|
| 289 |
-
</section>
|
| 290 |
-
</main>
|
| 291 |
-
|
| 292 |
-
<!-- Review Modal -->
|
| 293 |
-
<div id="reviewModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center p-4">
|
| 294 |
-
<div class="bg-white rounded-xl shadow-2xl w-full max-w-md p-6">
|
| 295 |
-
<div class="flex justify-between items-center mb-4">
|
| 296 |
-
<h3 class="text-lg font-bold">Write a review</h3>
|
| 297 |
-
<button onclick="closeReviewModal()" class="text-gray-400 hover:text-gray-700">
|
| 298 |
-
<i class="fas fa-times"></i>
|
| 299 |
-
</button>
|
| 300 |
-
</div>
|
| 301 |
-
<form id="reviewForm" class="space-y-3">
|
| 302 |
-
<div>
|
| 303 |
-
<label class="block text-sm font-medium mb-1">Rating</label>
|
| 304 |
-
<div class="flex gap-1" id="starPicker">
|
| 305 |
-
<button type="button" data-star="1" class="text-2xl text-gray-300 hover:text-amber-400">★</button>
|
| 306 |
-
<button type="button" data-star="2" class="text-2xl text-gray-300 hover:text-amber-400">★</button>
|
| 307 |
-
<button type="button" data-star="3" class="text-2xl text-gray-300 hover:text-amber-400">★</button>
|
| 308 |
-
<button type="button" data-star="4" class="text-2xl text-gray-300 hover:text-amber-400">★</button>
|
| 309 |
-
<button type="button" data-star="5" class="text-2xl text-gray-300 hover:text-amber-400">★</button>
|
| 310 |
-
</div>
|
| 311 |
-
<input type="hidden" id="reviewRating" value="0">
|
| 312 |
-
</div>
|
| 313 |
-
<div>
|
| 314 |
-
<label class="block text-sm font-medium mb-1">Title (optional)</label>
|
| 315 |
-
<input type="text" id="reviewTitle" class="w-full px-3 py-2 border rounded-lg" placeholder="Summarize your experience">
|
| 316 |
-
</div>
|
| 317 |
-
<div>
|
| 318 |
-
<label class="block text-sm font-medium mb-1">Comment</label>
|
| 319 |
-
<textarea id="reviewComment" rows="3" class="w-full px-3 py-2 border rounded-lg" placeholder="What did you like or dislike?"></textarea>
|
| 320 |
-
</div>
|
| 321 |
-
<div id="reviewError" class="text-sm text-red-500 hidden"></div>
|
| 322 |
-
<button type="submit" class="w-full bg-brand-500 text-white py-2 rounded-lg font-medium hover:bg-opacity-90">
|
| 323 |
-
Submit review
|
| 324 |
-
</button>
|
| 325 |
-
</form>
|
| 326 |
-
</div>
|
| 327 |
-
</div>
|
| 328 |
-
|
| 329 |
-
<!-- AI Assistant -->
|
| 330 |
-
<div class="fixed bottom-6 right-6 z-50" id="aiAssistant">
|
| 331 |
-
<button class="w-14 h-14 bg-brand-500 text-white rounded-full shadow-lg hover:bg-opacity-90 transition" id="aiToggle" aria-label="Open AI assistant">
|
| 332 |
-
<i class="fas fa-comment-dots text-2xl"></i>
|
| 333 |
-
</button>
|
| 334 |
-
<div class="hidden absolute bottom-16 right-0 w-96 bg-white rounded-lg shadow-xl" id="aiChat">
|
| 335 |
-
<div class="flex items-center justify-between p-4 border-b">
|
| 336 |
-
<h3 class="font-semibold text-lg">AI Shopping Assistant</h3>
|
| 337 |
-
<button class="text-gray-500 hover:text-gray-700" id="aiClose" aria-label="Close AI assistant">
|
| 338 |
-
<i class="fas fa-times"></i>
|
| 339 |
-
</button>
|
| 340 |
-
</div>
|
| 341 |
-
<div class="h-96 p-4 overflow-y-auto" id="aiMessages"></div>
|
| 342 |
-
<div class="p-4 border-t flex gap-2">
|
| 343 |
-
<input type="text" id="aiInput" placeholder="Ask me anything..."
|
| 344 |
-
class="flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:border-brand-500">
|
| 345 |
-
<button id="aiSend" class="bg-brand-500 text-white px-4 py-2 rounded-lg hover:bg-opacity-90" aria-label="Send message">
|
| 346 |
-
<i class="fas fa-paper-plane"></i>
|
| 347 |
-
</button>
|
| 348 |
-
</div>
|
| 349 |
-
</div>
|
| 350 |
-
</div>
|
| 351 |
-
|
| 352 |
-
<script src="../js/cart.js"></script>
|
| 353 |
-
<script src="js/product.js"></script>
|
| 354 |
-
</body>
|
| 355 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/profile.html
DELETED
|
@@ -1,356 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>My Profile - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* { font-family: 'Plus Jakarta Sans', system-ui, sans-serif; }
|
| 44 |
-
.loading { animation: spin 1s linear infinite; }
|
| 45 |
-
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 46 |
-
</style>
|
| 47 |
-
</head>
|
| 48 |
-
<body class="bg-gray-100 min-h-screen">
|
| 49 |
-
<!-- Header -->
|
| 50 |
-
<header class="bg-white shadow-md sticky top-0 z-50">
|
| 51 |
-
<nav class="container mx-auto px-4 py-4 flex justify-between items-center">
|
| 52 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 53 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 54 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 55 |
-
<defs>
|
| 56 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 57 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 58 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 59 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 60 |
-
</linearGradient>
|
| 61 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 62 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 63 |
-
<feMerge>
|
| 64 |
-
<feMergeNode in="coloredBlur"/>
|
| 65 |
-
<feMergeNode in="SourceGraphic"/>
|
| 66 |
-
</feMerge>
|
| 67 |
-
</filter>
|
| 68 |
-
</defs>
|
| 69 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 70 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 71 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 72 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 73 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 74 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 75 |
-
<g transform="translate(52, 8)">
|
| 76 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 77 |
-
</g>
|
| 78 |
-
</svg>
|
| 79 |
-
</div>
|
| 80 |
-
<div class="flex flex-col">
|
| 81 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 82 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 83 |
-
</div>
|
| 84 |
-
</a>
|
| 85 |
-
<div class="flex items-center gap-4">
|
| 86 |
-
<a href="../index.html" class="text-gray-600 hover:text-brand-500">
|
| 87 |
-
<i class="fas fa-home text-xl"></i>
|
| 88 |
-
</a>
|
| 89 |
-
<a href="cart.html" class="text-gray-600 hover:text-brand-500 relative">
|
| 90 |
-
<i class="fas fa-shopping-cart text-xl"></i>
|
| 91 |
-
<span id="cartCount" class="absolute -top-2 -right-2 bg-brand-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 92 |
-
</a>
|
| 93 |
-
</div>
|
| 94 |
-
</nav>
|
| 95 |
-
</header>
|
| 96 |
-
|
| 97 |
-
<!-- Main Content -->
|
| 98 |
-
<main class="container mx-auto px-4 py-8">
|
| 99 |
-
<!-- Back Link -->
|
| 100 |
-
<a href="../index.html" class="inline-flex items-center text-gray-600 hover:text-brand-500 mb-6">
|
| 101 |
-
<i class="fas fa-arrow-left mr-2"></i> Back to Home
|
| 102 |
-
</a>
|
| 103 |
-
|
| 104 |
-
<!-- Profile Card -->
|
| 105 |
-
<div class="max-w-4xl mx-auto">
|
| 106 |
-
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
|
| 107 |
-
<!-- Profile Header -->
|
| 108 |
-
<div class="bg-gradient-to-r from-primary to-red-400 p-8 text-white">
|
| 109 |
-
<div class="flex flex-col md:flex-row items-center gap-6">
|
| 110 |
-
<div class="relative">
|
| 111 |
-
<div id="avatarLarge" class="w-32 h-32 rounded-full bg-white text-brand-500 text-5xl font-bold flex items-center justify-center border-4 border-white shadow-lg">
|
| 112 |
-
U
|
| 113 |
-
</div>
|
| 114 |
-
<button id="changePhotoBtn" class="absolute bottom-0 right-0 bg-white text-brand-500 rounded-full p-2 shadow-lg hover:bg-gray-100">
|
| 115 |
-
<i class="fas fa-camera"></i>
|
| 116 |
-
</button>
|
| 117 |
-
</div>
|
| 118 |
-
<div class="text-center md:text-left">
|
| 119 |
-
<h1 id="userName" class="text-3xl font-bold">Loading...</h1>
|
| 120 |
-
<p id="userEmail" class="text-white/80 mt-1">Loading...</p>
|
| 121 |
-
<span class="inline-block mt-2 bg-white/20 px-3 py-1 rounded-full text-sm">
|
| 122 |
-
<i class="fas fa-crown mr-1"></i> Member since <span id="memberSince">2024</span>
|
| 123 |
-
</span>
|
| 124 |
-
</div>
|
| 125 |
-
</div>
|
| 126 |
-
</div>
|
| 127 |
-
|
| 128 |
-
<!-- Profile Stats -->
|
| 129 |
-
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 p-6 border-b">
|
| 130 |
-
<div class="text-center p-4 bg-gray-50 rounded-lg">
|
| 131 |
-
<i class="fas fa-shopping-bag text-2xl text-brand-500 mb-2"></i>
|
| 132 |
-
<p id="totalOrders" class="text-2xl font-bold">0</p>
|
| 133 |
-
<p class="text-gray-500 text-sm">Orders</p>
|
| 134 |
-
</div>
|
| 135 |
-
<div class="text-center p-4 bg-gray-50 rounded-lg">
|
| 136 |
-
<i class="fas fa-heart text-2xl text-red-400 mb-2"></i>
|
| 137 |
-
<p id="totalWishlist" class="text-2xl font-bold">0</p>
|
| 138 |
-
<p class="text-gray-500 text-sm">Wishlist</p>
|
| 139 |
-
</div>
|
| 140 |
-
<div class="text-center p-4 bg-gray-50 rounded-lg">
|
| 141 |
-
<i class="fas fa-star text-2xl text-yellow-400 mb-2"></i>
|
| 142 |
-
<p id="totalReviews" class="text-2xl font-bold">0</p>
|
| 143 |
-
<p class="text-gray-500 text-sm">Reviews</p>
|
| 144 |
-
</div>
|
| 145 |
-
<div class="text-center p-4 bg-gray-50 rounded-lg">
|
| 146 |
-
<i class="fas fa-wallet text-2xl text-green-500 mb-2"></i>
|
| 147 |
-
<p id="totalSpent" class="text-2xl font-bold">$0</p>
|
| 148 |
-
<p class="text-gray-500 text-sm">Spent</p>
|
| 149 |
-
</div>
|
| 150 |
-
</div>
|
| 151 |
-
|
| 152 |
-
<!-- Profile Form -->
|
| 153 |
-
<form id="profileForm" class="p-6 space-y-6">
|
| 154 |
-
<h2 class="text-xl font-semibold mb-4">
|
| 155 |
-
<i class="fas fa-user-edit mr-2 text-brand-500"></i> Personal Information
|
| 156 |
-
</h2>
|
| 157 |
-
|
| 158 |
-
<div class="grid md:grid-cols-2 gap-6">
|
| 159 |
-
<div>
|
| 160 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Full Name</label>
|
| 161 |
-
<input type="text" id="fullName" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="Enter your name">
|
| 162 |
-
</div>
|
| 163 |
-
<div>
|
| 164 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
|
| 165 |
-
<input type="email" id="emailDisplay" class="w-full px-4 py-2 border border-gray-300 rounded-lg bg-gray-100" disabled>
|
| 166 |
-
</div>
|
| 167 |
-
<div>
|
| 168 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Phone Number</label>
|
| 169 |
-
<input type="tel" id="phone" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="Enter your phone">
|
| 170 |
-
</div>
|
| 171 |
-
<div>
|
| 172 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Date of Birth</label>
|
| 173 |
-
<input type="date" id="dob" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
|
| 174 |
-
</div>
|
| 175 |
-
</div>
|
| 176 |
-
|
| 177 |
-
<div>
|
| 178 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Address</label>
|
| 179 |
-
<textarea id="address" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="Enter your address"></textarea>
|
| 180 |
-
</div>
|
| 181 |
-
|
| 182 |
-
<div class="grid md:grid-cols-3 gap-6">
|
| 183 |
-
<div>
|
| 184 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">City</label>
|
| 185 |
-
<input type="text" id="city" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="City">
|
| 186 |
-
</div>
|
| 187 |
-
<div>
|
| 188 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">State</label>
|
| 189 |
-
<input type="text" id="state" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="State">
|
| 190 |
-
</div>
|
| 191 |
-
<div>
|
| 192 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">ZIP Code</label>
|
| 193 |
-
<input type="text" id="zip" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="ZIP">
|
| 194 |
-
</div>
|
| 195 |
-
</div>
|
| 196 |
-
|
| 197 |
-
<div class="flex justify-end gap-4 pt-4">
|
| 198 |
-
<a href="../index.html" class="px-6 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Cancel</a>
|
| 199 |
-
<button type="submit" id="saveBtn" class="px-6 py-2 bg-brand-500 text-white rounded-lg hover:bg-brand-600 flex items-center">
|
| 200 |
-
<span>Save Changes</span>
|
| 201 |
-
<i class="fas fa-spinner loading ml-2 hidden"></i>
|
| 202 |
-
</button>
|
| 203 |
-
</div>
|
| 204 |
-
</form>
|
| 205 |
-
</div>
|
| 206 |
-
|
| 207 |
-
<!-- Quick Actions -->
|
| 208 |
-
<div class="mt-8 grid grid-cols-2 md:grid-cols-5 gap-4">
|
| 209 |
-
<a href="orders.html" class="bg-white p-6 rounded-xl shadow hover:shadow-lg transition text-center group">
|
| 210 |
-
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-3 group-hover:bg-blue-200">
|
| 211 |
-
<i class="fas fa-box text-blue-600 text-xl"></i>
|
| 212 |
-
</div>
|
| 213 |
-
<h3 class="font-semibold">My Orders</h3>
|
| 214 |
-
<p class="text-gray-500 text-sm">Track your orders</p>
|
| 215 |
-
</a>
|
| 216 |
-
<a href="wishlist.html" class="bg-white p-6 rounded-xl shadow hover:shadow-lg transition text-center group">
|
| 217 |
-
<div class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-3 group-hover:bg-red-200">
|
| 218 |
-
<i class="fas fa-heart text-red-500 text-xl"></i>
|
| 219 |
-
</div>
|
| 220 |
-
<h3 class="font-semibold">Wishlist</h3>
|
| 221 |
-
<p class="text-gray-500 text-sm">Saved items</p>
|
| 222 |
-
</a>
|
| 223 |
-
<a href="../seller/index.html" class="bg-white p-6 rounded-xl shadow hover:shadow-lg transition text-center group">
|
| 224 |
-
<div class="w-12 h-12 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-3 group-hover:bg-amber-200">
|
| 225 |
-
<i class="fas fa-store text-amber-600 text-xl"></i>
|
| 226 |
-
</div>
|
| 227 |
-
<h3 class="font-semibold">Seller Dashboard</h3>
|
| 228 |
-
<p class="text-gray-500 text-sm">Open your store</p>
|
| 229 |
-
</a>
|
| 230 |
-
<a href="cart.html" class="bg-white p-6 rounded-xl shadow hover:shadow-lg transition text-center group">
|
| 231 |
-
<div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-3 group-hover:bg-green-200">
|
| 232 |
-
<i class="fas fa-shopping-cart text-green-600 text-xl"></i>
|
| 233 |
-
</div>
|
| 234 |
-
<h3 class="font-semibold">My Cart</h3>
|
| 235 |
-
<p class="text-gray-500 text-sm">View cart items</p>
|
| 236 |
-
</a>
|
| 237 |
-
<a href="settings.html" class="bg-white p-6 rounded-xl shadow hover:shadow-lg transition text-center group">
|
| 238 |
-
<div class="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-3 group-hover:bg-purple-200">
|
| 239 |
-
<i class="fas fa-cog text-purple-600 text-xl"></i>
|
| 240 |
-
</div>
|
| 241 |
-
<h3 class="font-semibold">Settings</h3>
|
| 242 |
-
<p class="text-gray-500 text-sm">Account settings</p>
|
| 243 |
-
</a>
|
| 244 |
-
</div>
|
| 245 |
-
</div>
|
| 246 |
-
</main>
|
| 247 |
-
|
| 248 |
-
<!-- Footer -->
|
| 249 |
-
<footer class="bg-dark-900 text-white py-8 mt-12">
|
| 250 |
-
<div class="container mx-auto px-4 text-center">
|
| 251 |
-
<p>© 2025 Cellex. All rights reserved.</p>
|
| 252 |
-
</div>
|
| 253 |
-
</footer>
|
| 254 |
-
|
| 255 |
-
<script>
|
| 256 |
-
let currentUser = null;
|
| 257 |
-
let profileStats = null;
|
| 258 |
-
|
| 259 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 260 |
-
await checkAuth();
|
| 261 |
-
await loadProfile();
|
| 262 |
-
await loadStats();
|
| 263 |
-
await updateCartCount();
|
| 264 |
-
});
|
| 265 |
-
|
| 266 |
-
async function checkAuth() {
|
| 267 |
-
const { success, user } = await window.EdgeFunctions.auth.checkSession();
|
| 268 |
-
if (!success || !user) {
|
| 269 |
-
window.location.href = 'login.html?redirect=profile';
|
| 270 |
-
return;
|
| 271 |
-
}
|
| 272 |
-
currentUser = user;
|
| 273 |
-
}
|
| 274 |
-
|
| 275 |
-
async function loadProfile() {
|
| 276 |
-
if (!currentUser) return;
|
| 277 |
-
|
| 278 |
-
// Display basic info from auth
|
| 279 |
-
const fullName = currentUser.user_metadata?.full_name ||
|
| 280 |
-
currentUser.user_metadata?.name ||
|
| 281 |
-
currentUser.email?.split('@')[0] || 'User';
|
| 282 |
-
const initial = fullName.charAt(0).toUpperCase();
|
| 283 |
-
|
| 284 |
-
document.getElementById('userName').textContent = fullName;
|
| 285 |
-
document.getElementById('userEmail').textContent = currentUser.email;
|
| 286 |
-
document.getElementById('avatarLarge').textContent = initial;
|
| 287 |
-
document.getElementById('emailDisplay').value = currentUser.email;
|
| 288 |
-
document.getElementById('fullName').value = fullName;
|
| 289 |
-
|
| 290 |
-
if (currentUser.created_at) {
|
| 291 |
-
document.getElementById('memberSince').textContent = new Date(currentUser.created_at).getFullYear();
|
| 292 |
-
}
|
| 293 |
-
|
| 294 |
-
// Load additional profile + stats from edge function
|
| 295 |
-
const { success, profile, stats } = await window.EdgeFunctions.profile.get();
|
| 296 |
-
|
| 297 |
-
if (success && profile) {
|
| 298 |
-
document.getElementById('phone').value = profile.phone || '';
|
| 299 |
-
document.getElementById('dob').value = profile.dob || '';
|
| 300 |
-
document.getElementById('address').value = profile.address || '';
|
| 301 |
-
document.getElementById('city').value = profile.city || '';
|
| 302 |
-
document.getElementById('state').value = profile.state || '';
|
| 303 |
-
document.getElementById('zip').value = profile.zip || '';
|
| 304 |
-
}
|
| 305 |
-
profileStats = stats;
|
| 306 |
-
}
|
| 307 |
-
|
| 308 |
-
async function loadStats() {
|
| 309 |
-
if (!currentUser || !profileStats) return;
|
| 310 |
-
|
| 311 |
-
document.getElementById('totalOrders').textContent = profileStats.ordersCount || 0;
|
| 312 |
-
document.getElementById('totalWishlist').textContent = profileStats.wishlistCount || 0;
|
| 313 |
-
document.getElementById('totalReviews').textContent = profileStats.reviewsCount || 0;
|
| 314 |
-
document.getElementById('totalSpent').textContent = '$' + (profileStats.totalSpent || 0).toFixed(0);
|
| 315 |
-
}
|
| 316 |
-
|
| 317 |
-
async function updateCartCount() {
|
| 318 |
-
if (window.Cart && window.Cart.updateCartCountUI) {
|
| 319 |
-
window.Cart.updateCartCountUI();
|
| 320 |
-
}
|
| 321 |
-
}
|
| 322 |
-
|
| 323 |
-
// Handle form submission
|
| 324 |
-
document.getElementById('profileForm').addEventListener('submit', async (e) => {
|
| 325 |
-
e.preventDefault();
|
| 326 |
-
const saveBtn = document.getElementById('saveBtn');
|
| 327 |
-
const loadingIcon = saveBtn.querySelector('.loading');
|
| 328 |
-
|
| 329 |
-
loadingIcon.classList.remove('hidden');
|
| 330 |
-
saveBtn.disabled = true;
|
| 331 |
-
|
| 332 |
-
try {
|
| 333 |
-
const fullName = document.getElementById('fullName').value;
|
| 334 |
-
|
| 335 |
-
const { success } = await window.EdgeFunctions.profile.update({
|
| 336 |
-
fullName,
|
| 337 |
-
phone: document.getElementById('phone').value,
|
| 338 |
-
address: document.getElementById('address').value
|
| 339 |
-
});
|
| 340 |
-
|
| 341 |
-
if (!success) throw new Error('Profile update failed');
|
| 342 |
-
|
| 343 |
-
alert('Profile updated successfully!');
|
| 344 |
-
document.getElementById('userName').textContent = fullName;
|
| 345 |
-
document.getElementById('avatarLarge').textContent = fullName.charAt(0).toUpperCase();
|
| 346 |
-
} catch (error) {
|
| 347 |
-
console.error('Error saving profile:', error);
|
| 348 |
-
alert('Failed to save profile. Please try again.');
|
| 349 |
-
} finally {
|
| 350 |
-
loadingIcon.classList.add('hidden');
|
| 351 |
-
saveBtn.disabled = false;
|
| 352 |
-
}
|
| 353 |
-
});
|
| 354 |
-
</script>
|
| 355 |
-
</body>
|
| 356 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/settings.html
DELETED
|
@@ -1,524 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Account Settings - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* { font-family: 'Plus Jakarta Sans', system-ui, sans-serif; }
|
| 44 |
-
.loading { animation: spin 1s linear infinite; }
|
| 45 |
-
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 46 |
-
</style>
|
| 47 |
-
</head>
|
| 48 |
-
<body class="bg-gray-100 min-h-screen">
|
| 49 |
-
<!-- Header -->
|
| 50 |
-
<header class="bg-white shadow-md sticky top-0 z-50">
|
| 51 |
-
<nav class="container mx-auto px-4 py-4 flex justify-between items-center">
|
| 52 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 53 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 54 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 55 |
-
<defs>
|
| 56 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 57 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 58 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 59 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 60 |
-
</linearGradient>
|
| 61 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 62 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 63 |
-
<feMerge>
|
| 64 |
-
<feMergeNode in="coloredBlur"/>
|
| 65 |
-
<feMergeNode in="SourceGraphic"/>
|
| 66 |
-
</feMerge>
|
| 67 |
-
</filter>
|
| 68 |
-
</defs>
|
| 69 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 70 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 71 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 72 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 73 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 74 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 75 |
-
<g transform="translate(52, 8)">
|
| 76 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 77 |
-
</g>
|
| 78 |
-
</svg>
|
| 79 |
-
</div>
|
| 80 |
-
<div class="flex flex-col">
|
| 81 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 82 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 83 |
-
</div>
|
| 84 |
-
</a>
|
| 85 |
-
<div class="flex items-center gap-4">
|
| 86 |
-
<a href="../index.html" class="text-gray-600 hover:text-brand-500">
|
| 87 |
-
<i class="fas fa-home text-xl"></i>
|
| 88 |
-
</a>
|
| 89 |
-
<a href="cart.html" class="text-gray-600 hover:text-brand-500 relative">
|
| 90 |
-
<i class="fas fa-shopping-cart text-xl"></i>
|
| 91 |
-
<span id="cartCount" class="absolute -top-2 -right-2 bg-brand-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 92 |
-
</a>
|
| 93 |
-
</div>
|
| 94 |
-
</nav>
|
| 95 |
-
</header>
|
| 96 |
-
|
| 97 |
-
<!-- Main Content -->
|
| 98 |
-
<main class="container mx-auto px-4 py-8">
|
| 99 |
-
<!-- Back Link -->
|
| 100 |
-
<a href="../index.html" class="inline-flex items-center text-gray-600 hover:text-brand-500 mb-6">
|
| 101 |
-
<i class="fas fa-arrow-left mr-2"></i> Back to Home
|
| 102 |
-
</a>
|
| 103 |
-
|
| 104 |
-
<div class="max-w-4xl mx-auto">
|
| 105 |
-
<!-- Settings Navigation -->
|
| 106 |
-
<div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8">
|
| 107 |
-
<div class="border-b">
|
| 108 |
-
<div class="flex overflow-x-auto">
|
| 109 |
-
<button onclick="showSection('account')" class="tab-btn active px-6 py-4 text-sm font-medium whitespace-nowrap border-b-2 border-brand-500 text-brand-500" data-tab="account">
|
| 110 |
-
<i class="fas fa-user mr-2"></i> Account
|
| 111 |
-
</button>
|
| 112 |
-
<button onclick="showSection('security')" class="tab-btn px-6 py-4 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-gray-500 hover:text-gray-700" data-tab="security">
|
| 113 |
-
<i class="fas fa-lock mr-2"></i> Security
|
| 114 |
-
</button>
|
| 115 |
-
<button onclick="showSection('notifications')" class="tab-btn px-6 py-4 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-gray-500 hover:text-gray-700" data-tab="notifications">
|
| 116 |
-
<i class="fas fa-bell mr-2"></i> Notifications
|
| 117 |
-
</button>
|
| 118 |
-
<button onclick="showSection('preferences')" class="tab-btn px-6 py-4 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-gray-500 hover:text-gray-700" data-tab="preferences">
|
| 119 |
-
<i class="fas fa-cog mr-2"></i> Preferences
|
| 120 |
-
</button>
|
| 121 |
-
</div>
|
| 122 |
-
</div>
|
| 123 |
-
|
| 124 |
-
<!-- Account Section -->
|
| 125 |
-
<div id="accountSection" class="p-6">
|
| 126 |
-
<h2 class="text-xl font-bold mb-6">Account Settings</h2>
|
| 127 |
-
|
| 128 |
-
<div class="space-y-6">
|
| 129 |
-
<!-- Profile Info -->
|
| 130 |
-
<div class="flex items-center gap-4 pb-6 border-b">
|
| 131 |
-
<div id="avatarLarge" class="w-20 h-20 rounded-full bg-brand-500 text-white text-3xl font-bold flex items-center justify-center">
|
| 132 |
-
U
|
| 133 |
-
</div>
|
| 134 |
-
<div>
|
| 135 |
-
<h3 id="userName" class="text-lg font-semibold">Loading...</h3>
|
| 136 |
-
<p id="userEmail" class="text-gray-500">Loading...</p>
|
| 137 |
-
<a href="profile.html" class="text-brand-500 text-sm hover:underline mt-1 inline-block">
|
| 138 |
-
Edit Profile <i class="fas fa-arrow-right ml-1"></i>
|
| 139 |
-
</a>
|
| 140 |
-
</div>
|
| 141 |
-
</div>
|
| 142 |
-
|
| 143 |
-
<!-- Account Info -->
|
| 144 |
-
<div>
|
| 145 |
-
<h3 class="font-semibold mb-4">Account Information</h3>
|
| 146 |
-
<div class="grid md:grid-cols-2 gap-4">
|
| 147 |
-
<div class="bg-gray-50 p-4 rounded-lg">
|
| 148 |
-
<p class="text-sm text-gray-500">Email</p>
|
| 149 |
-
<p id="emailDisplay" class="font-medium">-</p>
|
| 150 |
-
</div>
|
| 151 |
-
<div class="bg-gray-50 p-4 rounded-lg">
|
| 152 |
-
<p class="text-sm text-gray-500">Member Since</p>
|
| 153 |
-
<p id="memberSince" class="font-medium">-</p>
|
| 154 |
-
</div>
|
| 155 |
-
<div class="bg-gray-50 p-4 rounded-lg">
|
| 156 |
-
<p class="text-sm text-gray-500">Account Type</p>
|
| 157 |
-
<p class="font-medium flex items-center gap-2">
|
| 158 |
-
<i class="fas fa-crown text-yellow-500"></i> Buyer Account
|
| 159 |
-
</p>
|
| 160 |
-
</div>
|
| 161 |
-
<div class="bg-gray-50 p-4 rounded-lg">
|
| 162 |
-
<p class="text-sm text-gray-500">Last Login</p>
|
| 163 |
-
<p id="lastLogin" class="font-medium">-</p>
|
| 164 |
-
</div>
|
| 165 |
-
</div>
|
| 166 |
-
</div>
|
| 167 |
-
|
| 168 |
-
<!-- Seller Account Link -->
|
| 169 |
-
<div class="bg-gradient-to-r from-primary/10 to-red-100 p-6 rounded-lg border border-brand-500/20">
|
| 170 |
-
<div class="flex items-start gap-4">
|
| 171 |
-
<div class="w-12 h-12 bg-brand-500 rounded-full flex items-center justify-center text-white">
|
| 172 |
-
<i class="fas fa-store"></i>
|
| 173 |
-
</div>
|
| 174 |
-
<div class="flex-1">
|
| 175 |
-
<h3 class="font-semibold">Start Selling on Eesha</h3>
|
| 176 |
-
<p class="text-sm text-gray-600 mt-1">Turn your passion into profit. Create a seller account and start selling today!</p>
|
| 177 |
-
<a href="../eesha selling folder/merchant/signup.html" class="inline-block mt-3 bg-brand-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-brand-600">
|
| 178 |
-
Create Seller Account
|
| 179 |
-
</a>
|
| 180 |
-
</div>
|
| 181 |
-
</div>
|
| 182 |
-
</div>
|
| 183 |
-
</div>
|
| 184 |
-
</div>
|
| 185 |
-
|
| 186 |
-
<!-- Security Section -->
|
| 187 |
-
<div id="securitySection" class="p-6 hidden">
|
| 188 |
-
<h2 class="text-xl font-bold mb-6">Security Settings</h2>
|
| 189 |
-
|
| 190 |
-
<div class="space-y-6">
|
| 191 |
-
<!-- Change Password -->
|
| 192 |
-
<div class="border rounded-lg p-6">
|
| 193 |
-
<h3 class="font-semibold mb-4"><i class="fas fa-key mr-2 text-brand-500"></i> Change Password</h3>
|
| 194 |
-
<form id="passwordForm" class="space-y-4">
|
| 195 |
-
<div>
|
| 196 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Current Password</label>
|
| 197 |
-
<input type="password" id="currentPassword" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
|
| 198 |
-
</div>
|
| 199 |
-
<div>
|
| 200 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">New Password</label>
|
| 201 |
-
<input type="password" id="newPassword" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
|
| 202 |
-
</div>
|
| 203 |
-
<div>
|
| 204 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Confirm New Password</label>
|
| 205 |
-
<input type="password" id="confirmPassword" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
|
| 206 |
-
</div>
|
| 207 |
-
<button type="submit" class="bg-brand-500 text-white px-6 py-2 rounded-lg hover:bg-brand-600">
|
| 208 |
-
Update Password
|
| 209 |
-
</button>
|
| 210 |
-
</form>
|
| 211 |
-
</div>
|
| 212 |
-
|
| 213 |
-
<!-- Two Factor Auth -->
|
| 214 |
-
<div class="border rounded-lg p-6">
|
| 215 |
-
<div class="flex items-center justify-between">
|
| 216 |
-
<div>
|
| 217 |
-
<h3 class="font-semibold"><i class="fas fa-shield-alt mr-2 text-green-500"></i> Two-Factor Authentication</h3>
|
| 218 |
-
<p class="text-sm text-gray-500 mt-1">Add an extra layer of security to your account</p>
|
| 219 |
-
</div>
|
| 220 |
-
<span class="px-3 py-1 bg-gray-100 text-gray-500 rounded-full text-sm">Coming Soon</span>
|
| 221 |
-
</div>
|
| 222 |
-
</div>
|
| 223 |
-
|
| 224 |
-
<!-- Active Sessions -->
|
| 225 |
-
<div class="border rounded-lg p-6">
|
| 226 |
-
<h3 class="font-semibold mb-4"><i class="fas fa-laptop mr-2 text-blue-500"></i> Active Sessions</h3>
|
| 227 |
-
<div class="space-y-3">
|
| 228 |
-
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
| 229 |
-
<div class="flex items-center gap-3">
|
| 230 |
-
<i class="fas fa-desktop text-gray-400"></i>
|
| 231 |
-
<div>
|
| 232 |
-
<p class="font-medium">Current Session</p>
|
| 233 |
-
<p class="text-sm text-gray-500" id="currentSession">This device</p>
|
| 234 |
-
</div>
|
| 235 |
-
</div>
|
| 236 |
-
<span class="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Active</span>
|
| 237 |
-
</div>
|
| 238 |
-
</div>
|
| 239 |
-
<button onclick="signOutAllDevices()" class="mt-4 text-red-500 hover:underline text-sm">
|
| 240 |
-
<i class="fas fa-sign-out-alt mr-1"></i> Sign out of all other devices
|
| 241 |
-
</button>
|
| 242 |
-
</div>
|
| 243 |
-
</div>
|
| 244 |
-
</div>
|
| 245 |
-
|
| 246 |
-
<!-- Notifications Section -->
|
| 247 |
-
<div id="notificationsSection" class="p-6 hidden">
|
| 248 |
-
<h2 class="text-xl font-bold mb-6">Notification Settings</h2>
|
| 249 |
-
|
| 250 |
-
<div class="space-y-4">
|
| 251 |
-
<div class="flex items-center justify-between p-4 border rounded-lg">
|
| 252 |
-
<div>
|
| 253 |
-
<h3 class="font-medium">Order Updates</h3>
|
| 254 |
-
<p class="text-sm text-gray-500">Get notified about your order status</p>
|
| 255 |
-
</div>
|
| 256 |
-
<label class="relative inline-flex items-center cursor-pointer">
|
| 257 |
-
<input type="checkbox" checked class="sr-only peer" id="orderNotifications">
|
| 258 |
-
<div class="w-11 h-6 bg-gray-200 peer-focus:ring-2 peer-focus:ring-primary rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-500"></div>
|
| 259 |
-
</label>
|
| 260 |
-
</div>
|
| 261 |
-
|
| 262 |
-
<div class="flex items-center justify-between p-4 border rounded-lg">
|
| 263 |
-
<div>
|
| 264 |
-
<h3 class="font-medium">Promotional Emails</h3>
|
| 265 |
-
<p class="text-sm text-gray-500">Receive deals and promotional offers</p>
|
| 266 |
-
</div>
|
| 267 |
-
<label class="relative inline-flex items-center cursor-pointer">
|
| 268 |
-
<input type="checkbox" class="sr-only peer" id="promoNotifications">
|
| 269 |
-
<div class="w-11 h-6 bg-gray-200 peer-focus:ring-2 peer-focus:ring-primary rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-500"></div>
|
| 270 |
-
</label>
|
| 271 |
-
</div>
|
| 272 |
-
|
| 273 |
-
<div class="flex items-center justify-between p-4 border rounded-lg">
|
| 274 |
-
<div>
|
| 275 |
-
<h3 class="font-medium">Price Drop Alerts</h3>
|
| 276 |
-
<p class="text-sm text-gray-500">Get notified when items in your wishlist go on sale</p>
|
| 277 |
-
</div>
|
| 278 |
-
<label class="relative inline-flex items-center cursor-pointer">
|
| 279 |
-
<input type="checkbox" checked class="sr-only peer" id="priceDropNotifications">
|
| 280 |
-
<div class="w-11 h-6 bg-gray-200 peer-focus:ring-2 peer-focus:ring-primary rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-500"></div>
|
| 281 |
-
</label>
|
| 282 |
-
</div>
|
| 283 |
-
|
| 284 |
-
<div class="flex items-center justify-between p-4 border rounded-lg">
|
| 285 |
-
<div>
|
| 286 |
-
<h3 class="font-medium">Newsletter</h3>
|
| 287 |
-
<p class="text-sm text-gray-500">Weekly updates on new products and trends</p>
|
| 288 |
-
</div>
|
| 289 |
-
<label class="relative inline-flex items-center cursor-pointer">
|
| 290 |
-
<input type="checkbox" class="sr-only peer" id="newsletterNotifications">
|
| 291 |
-
<div class="w-11 h-6 bg-gray-200 peer-focus:ring-2 peer-focus:ring-primary rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-brand-500"></div>
|
| 292 |
-
</label>
|
| 293 |
-
</div>
|
| 294 |
-
</div>
|
| 295 |
-
|
| 296 |
-
<button onclick="saveNotificationSettings()" class="mt-6 bg-brand-500 text-white px-6 py-2 rounded-lg hover:bg-brand-600">
|
| 297 |
-
Save Preferences
|
| 298 |
-
</button>
|
| 299 |
-
</div>
|
| 300 |
-
|
| 301 |
-
<!-- Preferences Section -->
|
| 302 |
-
<div id="preferencesSection" class="p-6 hidden">
|
| 303 |
-
<h2 class="text-xl font-bold mb-6">Preferences</h2>
|
| 304 |
-
|
| 305 |
-
<div class="space-y-6">
|
| 306 |
-
<!-- Language -->
|
| 307 |
-
<div>
|
| 308 |
-
<label class="block text-sm font-medium text-gray-700 mb-2">Language</label>
|
| 309 |
-
<select class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary">
|
| 310 |
-
<option value="en" selected>English</option>
|
| 311 |
-
<option value="es">Spanish</option>
|
| 312 |
-
<option value="fr">French</option>
|
| 313 |
-
</select>
|
| 314 |
-
</div>
|
| 315 |
-
|
| 316 |
-
<!-- Currency -->
|
| 317 |
-
<div>
|
| 318 |
-
<label class="block text-sm font-medium text-gray-700 mb-2">Currency</label>
|
| 319 |
-
<select class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary">
|
| 320 |
-
<option value="usd" selected>USD ($)</option>
|
| 321 |
-
<option value="eur">EUR (€)</option>
|
| 322 |
-
<option value="gbp">GBP (£)</option>
|
| 323 |
-
</select>
|
| 324 |
-
</div>
|
| 325 |
-
|
| 326 |
-
<!-- Theme -->
|
| 327 |
-
<div>
|
| 328 |
-
<label class="block text-sm font-medium text-gray-700 mb-2">Theme</label>
|
| 329 |
-
<div class="flex gap-4">
|
| 330 |
-
<label class="flex items-center gap-2 cursor-pointer">
|
| 331 |
-
<input type="radio" name="theme" value="light" checked class="text-brand-500 focus:ring-primary">
|
| 332 |
-
<span>Light</span>
|
| 333 |
-
</label>
|
| 334 |
-
<label class="flex items-center gap-2 cursor-pointer">
|
| 335 |
-
<input type="radio" name="theme" value="dark" class="text-brand-500 focus:ring-primary">
|
| 336 |
-
<span>Dark</span>
|
| 337 |
-
</label>
|
| 338 |
-
<label class="flex items-center gap-2 cursor-pointer">
|
| 339 |
-
<input type="radio" name="theme" value="system" class="text-brand-500 focus:ring-primary">
|
| 340 |
-
<span>System</span>
|
| 341 |
-
</label>
|
| 342 |
-
</div>
|
| 343 |
-
</div>
|
| 344 |
-
|
| 345 |
-
<button onclick="savePreferences()" class="bg-brand-500 text-white px-6 py-2 rounded-lg hover:bg-brand-600">
|
| 346 |
-
Save Preferences
|
| 347 |
-
</button>
|
| 348 |
-
</div>
|
| 349 |
-
|
| 350 |
-
<!-- Danger Zone -->
|
| 351 |
-
<div class="mt-12 pt-6 border-t border-red-200">
|
| 352 |
-
<h3 class="text-red-600 font-semibold mb-4"><i class="fas fa-exclamation-triangle mr-2"></i> Danger Zone</h3>
|
| 353 |
-
|
| 354 |
-
<div class="bg-red-50 border border-red-200 rounded-lg p-6">
|
| 355 |
-
<h4 class="font-medium text-red-700">Delete Account</h4>
|
| 356 |
-
<p class="text-sm text-red-600 mt-1">Once you delete your account, there is no going back. Please be certain.</p>
|
| 357 |
-
<button onclick="deleteAccount()" class="mt-4 bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700">
|
| 358 |
-
<i class="fas fa-trash mr-2"></i> Delete My Account
|
| 359 |
-
</button>
|
| 360 |
-
</div>
|
| 361 |
-
</div>
|
| 362 |
-
</div>
|
| 363 |
-
</div>
|
| 364 |
-
|
| 365 |
-
<!-- Quick Links -->
|
| 366 |
-
<div class="mt-8 bg-white rounded-xl shadow-lg p-6">
|
| 367 |
-
<h3 class="font-semibold mb-4">Quick Links</h3>
|
| 368 |
-
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
| 369 |
-
<a href="profile.html" class="flex items-center gap-2 text-gray-600 hover:text-brand-500 p-3 rounded-lg hover:bg-gray-50">
|
| 370 |
-
<i class="fas fa-user"></i>
|
| 371 |
-
<span>My Profile</span>
|
| 372 |
-
</a>
|
| 373 |
-
<a href="orders.html" class="flex items-center gap-2 text-gray-600 hover:text-brand-500 p-3 rounded-lg hover:bg-gray-50">
|
| 374 |
-
<i class="fas fa-box"></i>
|
| 375 |
-
<span>My Orders</span>
|
| 376 |
-
</a>
|
| 377 |
-
<a href="wishlist.html" class="flex items-center gap-2 text-gray-600 hover:text-brand-500 p-3 rounded-lg hover:bg-gray-50">
|
| 378 |
-
<i class="fas fa-heart"></i>
|
| 379 |
-
<span>Wishlist</span>
|
| 380 |
-
</a>
|
| 381 |
-
<a href="cart.html" class="flex items-center gap-2 text-gray-600 hover:text-brand-500 p-3 rounded-lg hover:bg-gray-50">
|
| 382 |
-
<i class="fas fa-shopping-cart"></i>
|
| 383 |
-
<span>My Cart</span>
|
| 384 |
-
</a>
|
| 385 |
-
</div>
|
| 386 |
-
</div>
|
| 387 |
-
</div>
|
| 388 |
-
</main>
|
| 389 |
-
|
| 390 |
-
<!-- Footer -->
|
| 391 |
-
<footer class="bg-dark-900 text-white py-8 mt-12">
|
| 392 |
-
<div class="container mx-auto px-4 text-center">
|
| 393 |
-
<p>© 2025 Cellex. All rights reserved.</p>
|
| 394 |
-
</div>
|
| 395 |
-
</footer>
|
| 396 |
-
|
| 397 |
-
<script>
|
| 398 |
-
let currentUser = null;
|
| 399 |
-
|
| 400 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 401 |
-
await checkAuth();
|
| 402 |
-
await loadUserInfo();
|
| 403 |
-
await updateCartCount();
|
| 404 |
-
});
|
| 405 |
-
|
| 406 |
-
async function checkAuth() {
|
| 407 |
-
const { success, user } = await window.EdgeFunctions.auth.checkSession();
|
| 408 |
-
if (!success || !user) {
|
| 409 |
-
window.location.href = 'login.html?redirect=settings';
|
| 410 |
-
return;
|
| 411 |
-
}
|
| 412 |
-
currentUser = user;
|
| 413 |
-
}
|
| 414 |
-
|
| 415 |
-
async function loadUserInfo() {
|
| 416 |
-
if (!currentUser) return;
|
| 417 |
-
|
| 418 |
-
const fullName = currentUser.user_metadata?.full_name ||
|
| 419 |
-
currentUser.user_metadata?.name ||
|
| 420 |
-
currentUser.email?.split('@')[0] || 'User';
|
| 421 |
-
const initial = fullName.charAt(0).toUpperCase();
|
| 422 |
-
|
| 423 |
-
document.getElementById('userName').textContent = fullName;
|
| 424 |
-
document.getElementById('userEmail').textContent = currentUser.email;
|
| 425 |
-
document.getElementById('emailDisplay').textContent = currentUser.email;
|
| 426 |
-
document.getElementById('avatarLarge').textContent = initial;
|
| 427 |
-
|
| 428 |
-
if (currentUser.created_at) {
|
| 429 |
-
document.getElementById('memberSince').textContent = new Date(currentUser.created_at).toLocaleDateString('en-US', {
|
| 430 |
-
year: 'numeric',
|
| 431 |
-
month: 'long'
|
| 432 |
-
});
|
| 433 |
-
}
|
| 434 |
-
|
| 435 |
-
if (currentUser.last_sign_in_at) {
|
| 436 |
-
document.getElementById('lastLogin').textContent = new Date(currentUser.last_sign_in_at).toLocaleDateString('en-US', {
|
| 437 |
-
year: 'numeric',
|
| 438 |
-
month: 'short',
|
| 439 |
-
day: 'numeric'
|
| 440 |
-
});
|
| 441 |
-
}
|
| 442 |
-
|
| 443 |
-
document.getElementById('currentSession').textContent = navigator.userAgent.includes('Mobile') ? 'Mobile device' : 'Desktop';
|
| 444 |
-
}
|
| 445 |
-
|
| 446 |
-
function showSection(sectionName) {
|
| 447 |
-
// Hide all sections
|
| 448 |
-
document.querySelectorAll('[id$="Section"]').forEach(section => {
|
| 449 |
-
section.classList.add('hidden');
|
| 450 |
-
});
|
| 451 |
-
|
| 452 |
-
// Show selected section
|
| 453 |
-
document.getElementById(sectionName + 'Section').classList.remove('hidden');
|
| 454 |
-
|
| 455 |
-
// Update tab styles
|
| 456 |
-
document.querySelectorAll('.tab-btn').forEach(btn => {
|
| 457 |
-
btn.classList.remove('active', 'border-brand-500', 'text-brand-500');
|
| 458 |
-
btn.classList.add('border-transparent', 'text-gray-500');
|
| 459 |
-
});
|
| 460 |
-
|
| 461 |
-
const activeTab = document.querySelector(`[data-tab="${sectionName}"]`);
|
| 462 |
-
if (activeTab) {
|
| 463 |
-
activeTab.classList.add('active', 'border-brand-500', 'text-brand-500');
|
| 464 |
-
activeTab.classList.remove('border-transparent', 'text-gray-500');
|
| 465 |
-
}
|
| 466 |
-
}
|
| 467 |
-
|
| 468 |
-
// Password change
|
| 469 |
-
document.getElementById('passwordForm').addEventListener('submit', async (e) => {
|
| 470 |
-
e.preventDefault();
|
| 471 |
-
|
| 472 |
-
const newPassword = document.getElementById('newPassword').value;
|
| 473 |
-
const confirmPassword = document.getElementById('confirmPassword').value;
|
| 474 |
-
|
| 475 |
-
if (newPassword !== confirmPassword) {
|
| 476 |
-
alert('Passwords do not match!');
|
| 477 |
-
return;
|
| 478 |
-
}
|
| 479 |
-
|
| 480 |
-
if (newPassword.length < 6) {
|
| 481 |
-
alert('Password must be at least 6 characters');
|
| 482 |
-
return;
|
| 483 |
-
}
|
| 484 |
-
|
| 485 |
-
try {
|
| 486 |
-
// Password change not yet supported via edge functions
|
| 487 |
-
alert('Password change coming soon. Please contact support to update your password.');
|
| 488 |
-
document.getElementById('passwordForm').reset();
|
| 489 |
-
} catch (error) {
|
| 490 |
-
alert('Failed to update password: ' + error.message);
|
| 491 |
-
}
|
| 492 |
-
});
|
| 493 |
-
|
| 494 |
-
async function signOutAllDevices() {
|
| 495 |
-
if (!confirm('This will sign you out of all devices including this one. Continue?')) return;
|
| 496 |
-
|
| 497 |
-
try {
|
| 498 |
-
await window.EdgeFunctions.auth.logout();
|
| 499 |
-
window.location.href = 'login.html';
|
| 500 |
-
} catch (error) {
|
| 501 |
-
alert('Failed to sign out: ' + error.message);
|
| 502 |
-
}
|
| 503 |
-
}
|
| 504 |
-
|
| 505 |
-
function saveNotificationSettings() {
|
| 506 |
-
alert('Notification preferences saved!');
|
| 507 |
-
}
|
| 508 |
-
|
| 509 |
-
function savePreferences() {
|
| 510 |
-
alert('Preferences saved!');
|
| 511 |
-
}
|
| 512 |
-
|
| 513 |
-
function deleteAccount() {
|
| 514 |
-
alert('Account deletion coming soon. Please contact support to delete your account.');
|
| 515 |
-
}
|
| 516 |
-
|
| 517 |
-
async function updateCartCount() {
|
| 518 |
-
if (window.Cart && window.Cart.updateCartCountUI) {
|
| 519 |
-
window.Cart.updateCartCountUI();
|
| 520 |
-
}
|
| 521 |
-
}
|
| 522 |
-
</script>
|
| 523 |
-
</body>
|
| 524 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/signup.html
DELETED
|
@@ -1,135 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Sign Up - Cellex</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 9 |
-
<style>
|
| 10 |
-
.loading {
|
| 11 |
-
animation: spin 1s linear infinite;
|
| 12 |
-
}
|
| 13 |
-
@keyframes spin {
|
| 14 |
-
0% { transform: rotate(0deg); }
|
| 15 |
-
100% { transform: rotate(360deg); }
|
| 16 |
-
}
|
| 17 |
-
</style>
|
| 18 |
-
</head>
|
| 19 |
-
<body class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
|
| 20 |
-
<!-- Main Container -->
|
| 21 |
-
<div class="w-full max-w-md">
|
| 22 |
-
<!-- Logo -->
|
| 23 |
-
<div class="text-center mb-8">
|
| 24 |
-
<a href="../index.html" class="inline-block">
|
| 25 |
-
<h1 class="text-4xl font-extrabold text-[#f0474a]">Cellex</h1>
|
| 26 |
-
</a>
|
| 27 |
-
<h2 class="mt-4 text-3xl font-bold text-gray-900">Create your account</h2>
|
| 28 |
-
<p class="mt-2 text-gray-600">Get started with Cellex</p>
|
| 29 |
-
</div>
|
| 30 |
-
|
| 31 |
-
<!-- Signup Card -->
|
| 32 |
-
<div class="bg-white shadow-xl rounded-lg p-8">
|
| 33 |
-
<!-- Email/Password Form -->
|
| 34 |
-
<form id="signupForm" class="space-y-6">
|
| 35 |
-
<div>
|
| 36 |
-
<label for="email" class="block text-sm font-medium text-gray-700">Email address</label>
|
| 37 |
-
<input type="email" id="email" name="email" required
|
| 38 |
-
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-[#f0474a] focus:border-[#f0474a]">
|
| 39 |
-
</div>
|
| 40 |
-
|
| 41 |
-
<div>
|
| 42 |
-
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
|
| 43 |
-
<input type="password" id="password" name="password" required minlength="6"
|
| 44 |
-
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-[#f0474a] focus:border-[#f0474a]">
|
| 45 |
-
</div>
|
| 46 |
-
|
| 47 |
-
<button type="submit" id="signupButton"
|
| 48 |
-
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-white bg-[#f0474a] hover:bg-[#e43d40] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#f0474a]">
|
| 49 |
-
<span>Sign up</span>
|
| 50 |
-
<i class="fas fa-spinner loading ml-2 hidden"></i>
|
| 51 |
-
</button>
|
| 52 |
-
|
| 53 |
-
<!-- Message Area -->
|
| 54 |
-
<div id="messageArea" class="hidden text-sm text-center"></div>
|
| 55 |
-
</form>
|
| 56 |
-
|
| 57 |
-
<!-- Sign In Link -->
|
| 58 |
-
<p class="mt-6 text-center text-sm text-gray-600">
|
| 59 |
-
Already have an account?
|
| 60 |
-
<a href="login.html" class="font-medium text-[#f0474a] hover:underline">Sign in</a>
|
| 61 |
-
</p>
|
| 62 |
-
</div>
|
| 63 |
-
</div>
|
| 64 |
-
|
| 65 |
-
<!-- Scripts: Edge function client only (NO Supabase SDK) -->
|
| 66 |
-
<script src="../js/config/edge-functions.js?v=2"></script>
|
| 67 |
-
<script>
|
| 68 |
-
// Elements
|
| 69 |
-
const signupForm = document.getElementById('signupForm');
|
| 70 |
-
const signupButton = document.getElementById('signupButton');
|
| 71 |
-
const messageArea = document.getElementById('messageArea');
|
| 72 |
-
const loadingIcon = signupButton.querySelector('.loading');
|
| 73 |
-
|
| 74 |
-
// Helper Functions
|
| 75 |
-
function showMessage(message, isError = true) {
|
| 76 |
-
messageArea.textContent = message;
|
| 77 |
-
messageArea.classList.remove('hidden', 'text-red-600', 'text-green-600');
|
| 78 |
-
messageArea.classList.add(isError ? 'text-red-600' : 'text-green-600');
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
function hideMessage() {
|
| 82 |
-
messageArea.classList.add('hidden');
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
function setLoading(isLoading) {
|
| 86 |
-
loadingIcon.classList.toggle('hidden', !isLoading);
|
| 87 |
-
signupButton.disabled = isLoading;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
// Email/Password Sign Up via edge function
|
| 91 |
-
async function performSignUp() {
|
| 92 |
-
hideMessage();
|
| 93 |
-
setLoading(true);
|
| 94 |
-
|
| 95 |
-
const email = document.getElementById('email').value.trim();
|
| 96 |
-
const password = document.getElementById('password').value;
|
| 97 |
-
|
| 98 |
-
try {
|
| 99 |
-
const result = await window.EdgeFunctions.auth.signup(email, password);
|
| 100 |
-
|
| 101 |
-
if (!result.success) {
|
| 102 |
-
showMessage(result.error || 'Failed to sign up');
|
| 103 |
-
return;
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
// Check if user got a session (some Supabase configs require email verification)
|
| 107 |
-
if (result.session) {
|
| 108 |
-
// User is logged in immediately
|
| 109 |
-
showMessage('Sign-up successful! Redirecting...', false);
|
| 110 |
-
setTimeout(() => { window.location.href = '../index.html'; }, 1500);
|
| 111 |
-
} else {
|
| 112 |
-
// Email verification required
|
| 113 |
-
showMessage('Sign-up successful! Please check your email for a verification link.', false);
|
| 114 |
-
signupForm.reset();
|
| 115 |
-
}
|
| 116 |
-
} catch (err) {
|
| 117 |
-
showMessage(err.message || 'Failed to sign up');
|
| 118 |
-
} finally {
|
| 119 |
-
setLoading(false);
|
| 120 |
-
}
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
signupForm.addEventListener('submit', async (e) => {
|
| 124 |
-
e.preventDefault();
|
| 125 |
-
await performSignUp();
|
| 126 |
-
});
|
| 127 |
-
|
| 128 |
-
signupButton.type = 'button';
|
| 129 |
-
signupButton.addEventListener('click', async (e) => {
|
| 130 |
-
e.preventDefault();
|
| 131 |
-
await performSignUp();
|
| 132 |
-
});
|
| 133 |
-
</script>
|
| 134 |
-
</body>
|
| 135 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eesha buying folder/wishlist.html
DELETED
|
@@ -1,315 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>My Wishlist - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="../js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="../js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#fef3c7',
|
| 25 |
-
200: '#fde68a',
|
| 26 |
-
300: '#fcd34d',
|
| 27 |
-
400: '#fbbf24',
|
| 28 |
-
500: '#f59e0b',
|
| 29 |
-
600: '#d97706',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* { font-family: 'Plus Jakarta Sans', system-ui, sans-serif; }
|
| 44 |
-
.loading { animation: spin 1s linear infinite; }
|
| 45 |
-
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 46 |
-
</style>
|
| 47 |
-
</head>
|
| 48 |
-
<body class="bg-gray-100 min-h-screen">
|
| 49 |
-
<!-- Header -->
|
| 50 |
-
<header class="bg-white shadow-md sticky top-0 z-50">
|
| 51 |
-
<nav class="container mx-auto px-4 py-4 flex justify-between items-center">
|
| 52 |
-
<a href="../index.html" class="flex items-center gap-2 group">
|
| 53 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 54 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 55 |
-
<defs>
|
| 56 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 57 |
-
<stop offset="0%" stop-color="#fbbf24"/>
|
| 58 |
-
<stop offset="50%" stop-color="#f59e0b"/>
|
| 59 |
-
<stop offset="100%" stop-color="#d97706"/>
|
| 60 |
-
</linearGradient>
|
| 61 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 62 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 63 |
-
<feMerge>
|
| 64 |
-
<feMergeNode in="coloredBlur"/>
|
| 65 |
-
<feMergeNode in="SourceGraphic"/>
|
| 66 |
-
</feMerge>
|
| 67 |
-
</filter>
|
| 68 |
-
</defs>
|
| 69 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 70 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 71 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 72 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 73 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 74 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 75 |
-
<g transform="translate(52, 8)">
|
| 76 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#fbbf24"/>
|
| 77 |
-
</g>
|
| 78 |
-
</svg>
|
| 79 |
-
</div>
|
| 80 |
-
<div class="flex flex-col">
|
| 81 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 82 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 83 |
-
</div>
|
| 84 |
-
</a>
|
| 85 |
-
<div class="flex items-center gap-4">
|
| 86 |
-
<a href="../index.html" class="text-gray-600 hover:text-brand-500">
|
| 87 |
-
<i class="fas fa-home text-xl"></i>
|
| 88 |
-
</a>
|
| 89 |
-
<a href="cart.html" class="text-gray-600 hover:text-brand-500 relative">
|
| 90 |
-
<i class="fas fa-shopping-cart text-xl"></i>
|
| 91 |
-
<span id="cartCount" class="absolute -top-2 -right-2 bg-brand-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 92 |
-
</a>
|
| 93 |
-
</div>
|
| 94 |
-
</nav>
|
| 95 |
-
</header>
|
| 96 |
-
|
| 97 |
-
<!-- Main Content -->
|
| 98 |
-
<main class="container mx-auto px-4 py-8">
|
| 99 |
-
<!-- Back Link -->
|
| 100 |
-
<a href="../index.html" class="inline-flex items-center text-gray-600 hover:text-brand-500 mb-6">
|
| 101 |
-
<i class="fas fa-arrow-left mr-2"></i> Back to Home
|
| 102 |
-
</a>
|
| 103 |
-
|
| 104 |
-
<div class="max-w-6xl mx-auto">
|
| 105 |
-
<!-- Page Title -->
|
| 106 |
-
<div class="flex justify-between items-center mb-6">
|
| 107 |
-
<div>
|
| 108 |
-
<h1 class="text-3xl font-bold text-gray-900">My Wishlist</h1>
|
| 109 |
-
<p class="text-gray-500 mt-1"><span id="wishlistCount">0</span> items saved</p>
|
| 110 |
-
</div>
|
| 111 |
-
<div class="flex gap-2">
|
| 112 |
-
<button id="shareWishlistBtn" onclick="shareMyWishlist()" class="px-4 py-2 bg-gradient-to-r from-amber-400 to-amber-600 text-ink-900 font-bold rounded-lg hover:opacity-90 transition">
|
| 113 |
-
<i class="fas fa-share-alt mr-1"></i> Share wishlist
|
| 114 |
-
</button>
|
| 115 |
-
<button id="clearAllBtn" class="px-4 py-2 text-red-500 hover:bg-red-50 rounded-lg hidden">
|
| 116 |
-
<i class="fas fa-trash mr-2"></i> Clear All
|
| 117 |
-
</button>
|
| 118 |
-
</div>
|
| 119 |
-
</div>
|
| 120 |
-
|
| 121 |
-
<!-- Wishlist Grid -->
|
| 122 |
-
<div id="wishlistGrid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
| 123 |
-
<!-- Loading state -->
|
| 124 |
-
<div class="col-span-full bg-white rounded-lg shadow p-8 text-center">
|
| 125 |
-
<i class="fas fa-spinner loading text-4xl text-brand-500 mb-4"></i>
|
| 126 |
-
<p class="text-gray-500">Loading your wishlist...</p>
|
| 127 |
-
</div>
|
| 128 |
-
</div>
|
| 129 |
-
</div>
|
| 130 |
-
</main>
|
| 131 |
-
|
| 132 |
-
<!-- Footer -->
|
| 133 |
-
<footer class="bg-dark-900 text-white py-8 mt-12">
|
| 134 |
-
<div class="container mx-auto px-4 text-center">
|
| 135 |
-
<p>© 2025 Cellex. All rights reserved.</p>
|
| 136 |
-
</div>
|
| 137 |
-
</footer>
|
| 138 |
-
|
| 139 |
-
<script>
|
| 140 |
-
let currentUser = null;
|
| 141 |
-
|
| 142 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 143 |
-
await checkAuth();
|
| 144 |
-
await loadWishlist();
|
| 145 |
-
await updateCartCount();
|
| 146 |
-
});
|
| 147 |
-
|
| 148 |
-
async function checkAuth() {
|
| 149 |
-
const { success, user } = await window.EdgeFunctions.auth.checkSession();
|
| 150 |
-
if (!success || !user) {
|
| 151 |
-
window.location.href = 'login.html?redirect=wishlist';
|
| 152 |
-
return;
|
| 153 |
-
}
|
| 154 |
-
currentUser = user;
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
async function loadWishlist() {
|
| 158 |
-
if (!currentUser) return;
|
| 159 |
-
|
| 160 |
-
const result = await window.EdgeFunctions.wishlist.get();
|
| 161 |
-
|
| 162 |
-
if (!result.success) {
|
| 163 |
-
console.error('Error loading wishlist:', result.error);
|
| 164 |
-
document.getElementById('wishlistGrid').innerHTML = `
|
| 165 |
-
<div class="col-span-full bg-white rounded-lg shadow p-8 text-center text-red-500">
|
| 166 |
-
<i class="fas fa-exclamation-circle text-4xl mb-4"></i>
|
| 167 |
-
<p>Failed to load wishlist. Please try again.</p>
|
| 168 |
-
</div>
|
| 169 |
-
`;
|
| 170 |
-
return;
|
| 171 |
-
}
|
| 172 |
-
|
| 173 |
-
const wishlist = result.items || [];
|
| 174 |
-
document.getElementById('wishlistCount').textContent = wishlist.length;
|
| 175 |
-
document.getElementById('clearAllBtn').classList.toggle('hidden', !wishlist.length);
|
| 176 |
-
|
| 177 |
-
if (wishlist.length === 0) {
|
| 178 |
-
document.getElementById('wishlistGrid').innerHTML = `
|
| 179 |
-
<div class="col-span-full bg-white rounded-lg shadow p-8 text-center">
|
| 180 |
-
<i class="fas fa-heart text-6xl text-gray-300 mb-4"></i>
|
| 181 |
-
<h3 class="text-xl font-semibold text-gray-700 mb-2">Your wishlist is empty</h3>
|
| 182 |
-
<p class="text-gray-500 mb-6">Save items you love to buy them later!</p>
|
| 183 |
-
<a href="../index.html" class="inline-block bg-brand-500 text-white px-6 py-2 rounded-lg hover:bg-brand-600">
|
| 184 |
-
Start Shopping
|
| 185 |
-
</a>
|
| 186 |
-
</div>
|
| 187 |
-
`;
|
| 188 |
-
return;
|
| 189 |
-
}
|
| 190 |
-
|
| 191 |
-
document.getElementById('wishlistGrid').innerHTML = wishlist.map(item => {
|
| 192 |
-
const product = item.products;
|
| 193 |
-
if (!product) return '';
|
| 194 |
-
|
| 195 |
-
return `
|
| 196 |
-
<div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition group">
|
| 197 |
-
<div class="relative">
|
| 198 |
-
<img src="${product.image_url || product.image || 'https://via.placeholder.com/300'}"
|
| 199 |
-
alt="${product.name}"
|
| 200 |
-
class="w-full h-48 object-cover cursor-pointer"
|
| 201 |
-
onclick="viewProduct('${product.id}')">
|
| 202 |
-
<button onclick="removeFromWishlist('${item.id}')"
|
| 203 |
-
class="absolute top-2 right-2 w-8 h-8 bg-white rounded-full shadow flex items-center justify-center text-red-500 hover:bg-red-50">
|
| 204 |
-
<i class="fas fa-times"></i>
|
| 205 |
-
</button>
|
| 206 |
-
${product.stock === 0 ? `
|
| 207 |
-
<div class="absolute inset-0 bg-black/50 flex items-center justify-center">
|
| 208 |
-
<span class="bg-red-500 text-white px-3 py-1 rounded-full text-sm">Out of Stock</span>
|
| 209 |
-
</div>
|
| 210 |
-
` : ''}
|
| 211 |
-
</div>
|
| 212 |
-
<div class="p-4">
|
| 213 |
-
<p class="text-xs text-gray-500 uppercase tracking-wide">${product.category || 'Category'}</p>
|
| 214 |
-
<h3 class="font-semibold text-gray-900 mt-1 cursor-pointer hover:text-brand-500" onclick="viewProduct('${product.id}')">
|
| 215 |
-
${product.name}
|
| 216 |
-
</h3>
|
| 217 |
-
<div class="flex items-center justify-between mt-3">
|
| 218 |
-
<span class="text-xl font-bold text-brand-500">$${(product.price || 0).toFixed(2)}</span>
|
| 219 |
-
</div>
|
| 220 |
-
<button onclick="addToCart('${product.id}', '${product.stock}')"
|
| 221 |
-
class="w-full mt-4 py-2 rounded-lg text-center font-medium transition
|
| 222 |
-
${product.stock === 0
|
| 223 |
-
? 'bg-gray-200 text-gray-500 cursor-not-allowed'
|
| 224 |
-
: 'bg-brand-500 text-white hover:bg-brand-600'}">
|
| 225 |
-
<i class="fas fa-cart-plus mr-2"></i>
|
| 226 |
-
${product.stock === 0 ? 'Out of Stock' : 'Add to Cart'}
|
| 227 |
-
</button>
|
| 228 |
-
</div>
|
| 229 |
-
</div>
|
| 230 |
-
`;
|
| 231 |
-
}).join('');
|
| 232 |
-
}
|
| 233 |
-
|
| 234 |
-
function viewProduct(productId) {
|
| 235 |
-
window.location.href = `product.html?id=${productId}`;
|
| 236 |
-
}
|
| 237 |
-
|
| 238 |
-
async function removeFromWishlist(wishlistId) {
|
| 239 |
-
if (!confirm('Remove this item from your wishlist?')) return;
|
| 240 |
-
|
| 241 |
-
const result = await window.EdgeFunctions.wishlist.remove(wishlistId);
|
| 242 |
-
|
| 243 |
-
if (!result.success) {
|
| 244 |
-
alert('Failed to remove item. Please try again.');
|
| 245 |
-
return;
|
| 246 |
-
}
|
| 247 |
-
|
| 248 |
-
loadWishlist();
|
| 249 |
-
}
|
| 250 |
-
|
| 251 |
-
async function addToCart(productId, stock) {
|
| 252 |
-
if (stock === '0') return;
|
| 253 |
-
|
| 254 |
-
try {
|
| 255 |
-
const result = await window.Cart.addToCart(productId, 1);
|
| 256 |
-
|
| 257 |
-
if (!result.success) {
|
| 258 |
-
alert(result.message || 'Failed to add to cart. Please try again.');
|
| 259 |
-
return;
|
| 260 |
-
}
|
| 261 |
-
|
| 262 |
-
alert('Added to cart!');
|
| 263 |
-
updateCartCount();
|
| 264 |
-
} catch (error) {
|
| 265 |
-
console.error('Error adding to cart:', error);
|
| 266 |
-
alert('Failed to add to cart. Please try again.');
|
| 267 |
-
}
|
| 268 |
-
}
|
| 269 |
-
|
| 270 |
-
document.getElementById('clearAllBtn').addEventListener('click', async () => {
|
| 271 |
-
if (!confirm('Are you sure you want to clear your entire wishlist?')) return;
|
| 272 |
-
|
| 273 |
-
// The edge function only exposes single-item removal; loop over items.
|
| 274 |
-
const result = await window.EdgeFunctions.wishlist.get();
|
| 275 |
-
if (!result.success) {
|
| 276 |
-
alert('Failed to clear wishlist. Please try again.');
|
| 277 |
-
return;
|
| 278 |
-
}
|
| 279 |
-
|
| 280 |
-
const items = result.items || [];
|
| 281 |
-
for (const item of items) {
|
| 282 |
-
await window.EdgeFunctions.wishlist.remove(item.id);
|
| 283 |
-
}
|
| 284 |
-
|
| 285 |
-
loadWishlist();
|
| 286 |
-
});
|
| 287 |
-
|
| 288 |
-
async function updateCartCount() {
|
| 289 |
-
if (typeof window.Cart !== 'undefined' && window.Cart.updateCartCountUI) {
|
| 290 |
-
await window.Cart.updateCartCountUI();
|
| 291 |
-
}
|
| 292 |
-
}
|
| 293 |
-
|
| 294 |
-
// Phase 2 — share wishlist
|
| 295 |
-
async function shareMyWishlist() {
|
| 296 |
-
const r = await window.EdgeFunctions.wishlistShare.share('My Cellex Wishlist');
|
| 297 |
-
if (!r.success) {
|
| 298 |
-
alert('Failed to create share link: ' + (r.error || ''));
|
| 299 |
-
return;
|
| 300 |
-
}
|
| 301 |
-
const url = `${window.location.origin}${r.url}`;
|
| 302 |
-
// Open WhatsApp share
|
| 303 |
-
const text = `🛍️ Check out my wishlist on Cellex! ${url}`;
|
| 304 |
-
window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank');
|
| 305 |
-
// Also copy to clipboard
|
| 306 |
-
try {
|
| 307 |
-
await navigator.clipboard.writeText(url);
|
| 308 |
-
alert('Share link copied!\n\nShare it with friends: ' + url);
|
| 309 |
-
} catch {
|
| 310 |
-
window.prompt('Copy your share link:', url);
|
| 311 |
-
}
|
| 312 |
-
}
|
| 313 |
-
</script>
|
| 314 |
-
</body>
|
| 315 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,32 +1,56 @@
|
|
| 1 |
---
|
| 2 |
title: Cellex Web
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
|
|
|
| 7 |
pinned: false
|
| 8 |
-
license:
|
| 9 |
-
short_description:
|
| 10 |
---
|
| 11 |
|
| 12 |
-
# Cellex
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
## Architecture
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
| `SUPABASE_ANON_KEY` | (Supabase anon key) |
|
| 32 |
-
| `PORT` | `7860` |
|
|
|
|
| 1 |
---
|
| 2 |
title: Cellex Web
|
| 3 |
+
emoji: 🛍️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
short_description: Nigeria's #1 social commerce marketplace (Next.js)
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# Cellex — Nigeria's #1 Social Commerce Marketplace
|
| 14 |
|
| 15 |
+
Next.js 16 (App Router) frontend for the Cellex platform — a Nigerian social ecommerce
|
| 16 |
+
marketplace with live shopping, group buys, AI-powered discovery, and unified WhatsApp + Telegram
|
| 17 |
+
cross-platform shopping.
|
| 18 |
+
|
| 19 |
+
## What's inside
|
| 20 |
+
|
| 21 |
+
- **Buyer flow**: home, product detail, cart, checkout, PalmPay bank-transfer payment, orders, wishlist, profile
|
| 22 |
+
- **Discovery**: categories, search, seller storefronts, video feed, trending, AI recommendations
|
| 23 |
+
- **Live shopping**: browse live sessions, watch with real-time chat, featured product buy
|
| 24 |
+
- **Group buys**: Pinduoduo-style group discounts unlocked at target count
|
| 25 |
+
- **Cross-platform**: link WhatsApp bot with unified cart, Telegram channel alerts
|
| 26 |
+
- **Seller center**: dashboard, product CRUD, orders, profile, go-live, product videos, 24h stories
|
| 27 |
|
| 28 |
## Architecture
|
| 29 |
|
| 30 |
+
- Frontend: Next.js 16 App Router + React + TypeScript + Tailwind CSS 4 + shadcn/ui
|
| 31 |
+
- Backend: 27 Supabase Edge Functions (Deno) — unchanged from prior phases
|
| 32 |
+
- Auth: HTTP-only cookies → `web_sessions` table → JWT verify in edge functions
|
| 33 |
+
- Payment: PalmPay bank transfer with Gmail-IMAP auto-verification (Render bot)
|
| 34 |
+
- AI: Qwen2.5-72B powering product recommendations and shopping assistant
|
| 35 |
+
|
| 36 |
+
## Deployment
|
| 37 |
|
| 38 |
+
This Space uses the **Docker SDK**. The Dockerfile:
|
| 39 |
|
| 40 |
+
1. Builds the Next.js app with `output: "standalone"`
|
| 41 |
+
2. Copies the standalone server + static assets to a slim Node 20 image
|
| 42 |
+
3. Serves on port **7860** (HF Spaces default)
|
| 43 |
+
|
| 44 |
+
## Environment variables (set in HF Space settings)
|
| 45 |
+
|
| 46 |
+
```
|
| 47 |
+
SUPABASE_URL=https://tcwdbokruvlizkxcpkzj.supabase.co
|
| 48 |
+
SUPABASE_ANON_KEY=... (Supabase anon key)
|
| 49 |
+
SUPABASE_SERVICE_KEY=... (optional, only if you upload videos via web-server)
|
| 50 |
+
GMAIL_EMAIL=... (optional, only if running payment verifier here)
|
| 51 |
+
GMAIL_APP_PASSWORD=...
|
| 52 |
+
```
|
| 53 |
|
| 54 |
+
> **Note**: Most existing edge functions, the payment verifier, and WhatsApp/Telegram bots
|
| 55 |
+
> are NOT run here — they continue running on Render / Supabase. This Space only hosts the
|
| 56 |
+
> Next.js web frontend.
|
|
|
|
|
|
ai-chat.html
DELETED
|
@@ -1,235 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
-
<title>AI Shop — Cellex AI Shopping Assistant</title>
|
| 7 |
-
<meta name="description" content="Chat with Cellex's AI assistant to find products, compare prices, and get shopping recommendations.">
|
| 8 |
-
|
| 9 |
-
<!-- Fonts & Icons -->
|
| 10 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 11 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 12 |
-
|
| 13 |
-
<!-- Tailwind only - NO Supabase SDK in frontend -->
|
| 14 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 15 |
-
|
| 16 |
-
<script>
|
| 17 |
-
tailwind.config = {
|
| 18 |
-
theme: {
|
| 19 |
-
extend: {
|
| 20 |
-
fontFamily: {
|
| 21 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 22 |
-
},
|
| 23 |
-
colors: {
|
| 24 |
-
brand: {
|
| 25 |
-
50: '#ecfeff',
|
| 26 |
-
100: '#cffafe',
|
| 27 |
-
200: '#a5f3fc',
|
| 28 |
-
300: '#67e8f9',
|
| 29 |
-
400: '#00d4ff',
|
| 30 |
-
500: '#00d4ff',
|
| 31 |
-
600: '#0891b2',
|
| 32 |
-
700: '#0e7490',
|
| 33 |
-
},
|
| 34 |
-
dark: {
|
| 35 |
-
900: '#0f172a',
|
| 36 |
-
800: '#1e293b',
|
| 37 |
-
700: '#334155',
|
| 38 |
-
}
|
| 39 |
-
},
|
| 40 |
-
animation: {
|
| 41 |
-
'slide-up': 'slideUp 0.3s ease-out',
|
| 42 |
-
'fade-in': 'fadeIn 0.3s ease-out',
|
| 43 |
-
'typing': 'typing 1.5s infinite',
|
| 44 |
-
},
|
| 45 |
-
keyframes: {
|
| 46 |
-
slideUp: {
|
| 47 |
-
'0%': { opacity: '0', transform: 'translateY(10px)' },
|
| 48 |
-
'100%': { opacity: '1', transform: 'translateY(0)' },
|
| 49 |
-
},
|
| 50 |
-
fadeIn: {
|
| 51 |
-
'0%': { opacity: '0' },
|
| 52 |
-
'100%': { opacity: '1' },
|
| 53 |
-
},
|
| 54 |
-
typing: {
|
| 55 |
-
'0%, 60%, 100%': { opacity: '0.3' },
|
| 56 |
-
'30%': { opacity: '1' },
|
| 57 |
-
},
|
| 58 |
-
}
|
| 59 |
-
}
|
| 60 |
-
}
|
| 61 |
-
}
|
| 62 |
-
</script>
|
| 63 |
-
|
| 64 |
-
<style>
|
| 65 |
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 66 |
-
|
| 67 |
-
html { scroll-behavior: smooth; }
|
| 68 |
-
|
| 69 |
-
body {
|
| 70 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 71 |
-
background: #f9fafb;
|
| 72 |
-
color: #1f2937;
|
| 73 |
-
overflow: hidden;
|
| 74 |
-
height: 100vh;
|
| 75 |
-
height: 100dvh;
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
.gradient-text {
|
| 79 |
-
background: linear-gradient(135deg, #00d4ff 0%, #00d4ff 50%, #0891b2 100%);
|
| 80 |
-
-webkit-background-clip: text;
|
| 81 |
-
-webkit-text-fill-color: transparent;
|
| 82 |
-
background-clip: text;
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
.glass {
|
| 86 |
-
background: rgba(255, 255, 255, 0.95);
|
| 87 |
-
backdrop-filter: blur(20px);
|
| 88 |
-
-webkit-backdrop-filter: blur(20px);
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
/* Chat Messages */
|
| 92 |
-
.chat-container {
|
| 93 |
-
display: flex;
|
| 94 |
-
flex-direction: column;
|
| 95 |
-
height: calc(100vh - 120px - 70px);
|
| 96 |
-
height: calc(100dvh - 120px - 70px);
|
| 97 |
-
overflow-y: auto;
|
| 98 |
-
padding-bottom: 16px;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
.chat-container::-webkit-scrollbar { width: 4px; }
|
| 102 |
-
.chat-container::-webkit-scrollbar-track { background: transparent; }
|
| 103 |
-
.chat-container::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; }
|
| 104 |
-
|
| 105 |
-
.message-bubble {
|
| 106 |
-
max-width: 80%;
|
| 107 |
-
animation: slideUp 0.3s ease-out;
|
| 108 |
-
}
|
| 109 |
-
|
| 110 |
-
/* Typing Indicator */
|
| 111 |
-
.typing-dot {
|
| 112 |
-
width: 8px;
|
| 113 |
-
height: 8px;
|
| 114 |
-
border-radius: 50%;
|
| 115 |
-
background: #9ca3af;
|
| 116 |
-
display: inline-block;
|
| 117 |
-
margin: 0 2px;
|
| 118 |
-
}
|
| 119 |
-
.typing-dot:nth-child(1) { animation: typing 1.4s infinite 0s; }
|
| 120 |
-
.typing-dot:nth-child(2) { animation: typing 1.4s infinite 0.2s; }
|
| 121 |
-
.typing-dot:nth-child(3) { animation: typing 1.4s infinite 0.4s; }
|
| 122 |
-
|
| 123 |
-
/* Product card in chat */
|
| 124 |
-
.chat-product-card {
|
| 125 |
-
transition: all 0.2s ease;
|
| 126 |
-
}
|
| 127 |
-
.chat-product-card:hover {
|
| 128 |
-
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
| 129 |
-
transform: translateY(-2px);
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
/* Mobile Bottom Nav */
|
| 133 |
-
.mobile-nav {
|
| 134 |
-
padding-bottom: env(safe-area-inset-bottom);
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
/* Input focus */
|
| 138 |
-
.chat-input:focus-within {
|
| 139 |
-
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.3);
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
/* Quick action chip */
|
| 143 |
-
.chip {
|
| 144 |
-
transition: all 0.2s ease;
|
| 145 |
-
}
|
| 146 |
-
.chip:hover {
|
| 147 |
-
background: #cffafe;
|
| 148 |
-
border-color: #00d4ff;
|
| 149 |
-
}
|
| 150 |
-
.chip:active {
|
| 151 |
-
transform: scale(0.97);
|
| 152 |
-
}
|
| 153 |
-
</style>
|
| 154 |
-
</head>
|
| 155 |
-
|
| 156 |
-
<body class="min-h-screen">
|
| 157 |
-
|
| 158 |
-
<!-- Universal Navbar -->
|
| 159 |
-
<div id="nav-mount"></div>
|
| 160 |
-
<script src="js/load-navbar.js"></script>
|
| 161 |
-
|
| 162 |
-
<!-- Header -->
|
| 163 |
-
|
| 164 |
-
<!-- Chat Area -->
|
| 165 |
-
<div id="chatContainer" class="chat-container px-4">
|
| 166 |
-
<!-- Welcome Message -->
|
| 167 |
-
<div class="flex items-start gap-2.5 mb-4 mt-4 animate-fade-in">
|
| 168 |
-
<div class="w-8 h-8 bg-gradient-to-br from-cyan-400 to-cyan-700 rounded-full flex items-center justify-center flex-shrink-0 shadow-sm">
|
| 169 |
-
<i class="fas fa-robot text-white text-xs"></i>
|
| 170 |
-
</div>
|
| 171 |
-
<div class="message-bubble bg-white rounded-2xl rounded-tl-md p-3.5 shadow-sm border border-gray-100">
|
| 172 |
-
<p class="text-sm text-gray-700 leading-relaxed">Hi! I'm your <strong class="text-cyan-500">Cellex AI Assistant</strong>. I can help you find products, compare prices, track deals, and more! What are you looking for today?</p>
|
| 173 |
-
<p class="text-[10px] text-gray-400 mt-1.5">Just now</p>
|
| 174 |
-
</div>
|
| 175 |
-
</div>
|
| 176 |
-
|
| 177 |
-
<!-- Quick Suggestion Chips -->
|
| 178 |
-
<div id="suggestionChips" class="flex flex-wrap gap-2 mb-4 ml-10">
|
| 179 |
-
<button onclick="sendSuggestion('Show me phones under ₦50,000')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 180 |
-
<i class="fas fa-mobile-alt text-cyan-400"></i>
|
| 181 |
-
Phones under ₦50k
|
| 182 |
-
</button>
|
| 183 |
-
<button onclick="sendSuggestion('Best deals today')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 184 |
-
<i class="fas fa-bolt text-cyan-400"></i>
|
| 185 |
-
Best deals today
|
| 186 |
-
</button>
|
| 187 |
-
<button onclick="sendSuggestion('Farm fresh produce')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 188 |
-
<i class="fas fa-leaf text-cyan-400"></i>
|
| 189 |
-
Farm fresh produce
|
| 190 |
-
</button>
|
| 191 |
-
<button onclick="sendSuggestion('Electronics')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 192 |
-
<i class="fas fa-laptop text-cyan-400"></i>
|
| 193 |
-
Electronics
|
| 194 |
-
</button>
|
| 195 |
-
</div>
|
| 196 |
-
</div>
|
| 197 |
-
|
| 198 |
-
<!-- Chat Input Bar -->
|
| 199 |
-
<div id="chatInputBar" class="fixed bottom-[70px] left-0 right-0 bg-white border-t border-gray-200 px-3 py-2.5 z-40" style="padding-bottom: calc(0.625rem + env(safe-area-inset-bottom));">
|
| 200 |
-
<div class="max-w-3xl mx-auto">
|
| 201 |
-
<!-- Image Preview (inline, above input) -->
|
| 202 |
-
<div id="ai-image-preview" class="hidden mb-2 p-2 bg-gray-50 rounded-xl border border-gray-200">
|
| 203 |
-
<div class="flex items-end gap-2.5">
|
| 204 |
-
<div class="relative flex-shrink-0">
|
| 205 |
-
<img id="ai-preview-img" src="" alt="Preview" class="w-20 h-20 rounded-xl object-cover border-2 border-gray-200">
|
| 206 |
-
<button id="ai-remove-image" class="absolute -top-2 -right-2 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center text-[10px] shadow-md hover:bg-red-600 transition-colors"><i class="fas fa-times"></i></button>
|
| 207 |
-
</div>
|
| 208 |
-
<p class="text-xs text-gray-500 pb-1">Image attached — send a message to ask about it</p>
|
| 209 |
-
</div>
|
| 210 |
-
</div>
|
| 211 |
-
<div class="chat-input flex items-center gap-2 bg-gray-100 rounded-full px-3 py-2 border-2 border-transparent transition-all">
|
| 212 |
-
<input type="file" id="ai-file-input" accept="image/*" hidden>
|
| 213 |
-
<button id="ai-upload-btn" onclick="document.getElementById('ai-file-input').click()" class="w-9 h-9 flex items-center justify-center text-gray-400 hover:text-cyan-500 hover:bg-cyan-50 rounded-full transition-all flex-shrink-0" title="Upload image">
|
| 214 |
-
<i class="fas fa-plus text-lg"></i>
|
| 215 |
-
</button>
|
| 216 |
-
<input type="text" id="chatInput" placeholder="Ask me anything about products..."
|
| 217 |
-
class="flex-1 bg-transparent outline-none text-sm"
|
| 218 |
-
onkeypress="if(event.key==='Enter') sendMessage()">
|
| 219 |
-
<button onclick="sendMessage()" class="w-9 h-9 bg-gradient-to-r from-cyan-400 to-cyan-600 rounded-full flex items-center justify-center hover:from-cyan-500 hover:to-cyan-700 transition-all shadow-md flex-shrink-0">
|
| 220 |
-
<i class="fas fa-paper-plane text-white text-sm"></i>
|
| 221 |
-
</button>
|
| 222 |
-
</div>
|
| 223 |
-
<p class="text-center text-[10px] text-gray-400 mt-1.5">Powered by Cellex AI</p>
|
| 224 |
-
</div>
|
| 225 |
-
</div>
|
| 226 |
-
|
| 227 |
-
<!-- Bottom Navigation -->
|
| 228 |
-
|
| 229 |
-
<!-- Scripts: Supabase config + Cart module + AI Assistant (shared logic) -->
|
| 230 |
-
<!-- Scripts: Edge function client (no Supabase SDK) + Cart + AI Assistant -->
|
| 231 |
-
<script src="js/config/edge-functions.js?v=2"></script>
|
| 232 |
-
<script src="js/cart.js?v=10"></script>
|
| 233 |
-
<script src="js/ai-buyer-assistant.js?v=13"></script>
|
| 234 |
-
</body>
|
| 235 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auth.html
DELETED
|
@@ -1,246 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Login / Signup - Eesha</title>
|
| 7 |
-
<link rel="stylesheet" href="css/style.css">
|
| 8 |
-
|
| 9 |
-
</head>
|
| 10 |
-
<body class="auth-body">
|
| 11 |
-
|
| 12 |
-
<div class="auth-container">
|
| 13 |
-
<div id="login-form">
|
| 14 |
-
<h2>Login to Eesha</h2>
|
| 15 |
-
<form id="login">
|
| 16 |
-
<input type="email" id="login-email" placeholder="Email" required>
|
| 17 |
-
<input type="password" id="login-password" placeholder="Password" required>
|
| 18 |
-
<button type="submit">Login</button>
|
| 19 |
-
</form>
|
| 20 |
-
<p>Don't have an account? <a href="#" id="show-signup">Sign up</a></p>
|
| 21 |
-
</div>
|
| 22 |
-
|
| 23 |
-
<div id="signup-form" style="display:none;">
|
| 24 |
-
<h2>Create an Account</h2>
|
| 25 |
-
<form id="signup">
|
| 26 |
-
<input type="text" id="signup-name" placeholder="Full Name" required>
|
| 27 |
-
<input type="email" id="signup-email" placeholder="Email" required>
|
| 28 |
-
<input type="password" id="signup-password" placeholder="Password" required>
|
| 29 |
-
<div class="user-type">
|
| 30 |
-
<label>I am a:</label>
|
| 31 |
-
<input type="radio" id="buyer" name="user_type" value="buyer" checked>
|
| 32 |
-
<label for="buyer">Buyer</label>
|
| 33 |
-
<input type="radio" id="seller" name="user_type" value="seller">
|
| 34 |
-
<label for="seller">Seller</label>
|
| 35 |
-
</div>
|
| 36 |
-
<button type="submit">Sign Up</button>
|
| 37 |
-
</form>
|
| 38 |
-
<p>Already have an account? <a href="#" id="show-login">Log in</a></p>
|
| 39 |
-
</div>
|
| 40 |
-
<a href="seller/login.html" style="text-decoration: none;"> Am a Seller</a>
|
| 41 |
-
</div>
|
| 42 |
-
<script>/* General Body & Layout */
|
| 43 |
-
body {
|
| 44 |
-
font-family: Arial, sans-serif;
|
| 45 |
-
margin: 0;
|
| 46 |
-
background-color: #f4f4f4;
|
| 47 |
-
color: #333;
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
.container {
|
| 51 |
-
width: 90%;
|
| 52 |
-
margin: 0 auto;
|
| 53 |
-
max-width: 1200px;
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
/* Header */
|
| 57 |
-
header {
|
| 58 |
-
background-color: #2c3e50;
|
| 59 |
-
color: white;
|
| 60 |
-
padding: 1rem 0;
|
| 61 |
-
display: flex;
|
| 62 |
-
justify-content: space-between;
|
| 63 |
-
align-items: center;
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
header h1 {
|
| 67 |
-
margin: 0;
|
| 68 |
-
font-size: 1.8rem;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
header nav a, header nav button {
|
| 72 |
-
color: white;
|
| 73 |
-
text-decoration: none;
|
| 74 |
-
background: #3498db;
|
| 75 |
-
padding: 0.5rem 1rem;
|
| 76 |
-
border-radius: 5px;
|
| 77 |
-
border: none;
|
| 78 |
-
cursor: pointer;
|
| 79 |
-
}
|
| 80 |
-
header nav span {
|
| 81 |
-
margin-right: 15px;
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
/* Product Grid */
|
| 85 |
-
.product-grid {
|
| 86 |
-
display: grid;
|
| 87 |
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
| 88 |
-
gap: 1.5rem;
|
| 89 |
-
margin-top: 2rem;
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
.product-card {
|
| 93 |
-
background-color: white;
|
| 94 |
-
border: 1px solid #ddd;
|
| 95 |
-
border-radius: 8px;
|
| 96 |
-
overflow: hidden;
|
| 97 |
-
text-align: center;
|
| 98 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 99 |
-
cursor: pointer;
|
| 100 |
-
transition: transform 0.2s, box-shadow 0.2s;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
.product-card:hover {
|
| 104 |
-
transform: translateY(-5px);
|
| 105 |
-
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
.product-card img {
|
| 109 |
-
width: 100%;
|
| 110 |
-
height: 180px;
|
| 111 |
-
object-fit: cover;
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
.product-card h3 {
|
| 115 |
-
margin: 0.5rem 0;
|
| 116 |
-
font-size: 1.1rem;
|
| 117 |
-
padding: 0 10px;
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
.product-card .price {
|
| 121 |
-
color: #e74c3c;
|
| 122 |
-
font-weight: bold;
|
| 123 |
-
font-size: 1.2rem;
|
| 124 |
-
margin-bottom: 1rem;
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
/* Auth Page */
|
| 128 |
-
.auth-body {
|
| 129 |
-
display: flex;
|
| 130 |
-
justify-content: center;
|
| 131 |
-
align-items: center;
|
| 132 |
-
height: 100vh;
|
| 133 |
-
background: linear-gradient(135deg, #3498db, #2c3e50);
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
.auth-container {
|
| 137 |
-
background-color: white;
|
| 138 |
-
padding: 2rem;
|
| 139 |
-
border-radius: 10px;
|
| 140 |
-
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
| 141 |
-
width: 100%;
|
| 142 |
-
max-width: 400px;
|
| 143 |
-
text-align: center;
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
.auth-container h2 {
|
| 147 |
-
margin-bottom: 1.5rem;
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
.auth-container input[type="text"],
|
| 151 |
-
.auth-container input[type="email"],
|
| 152 |
-
.auth-container input[type="password"] {
|
| 153 |
-
width: calc(100% - 20px);
|
| 154 |
-
padding: 10px;
|
| 155 |
-
margin-bottom: 1rem;
|
| 156 |
-
border: 1px solid #ccc;
|
| 157 |
-
border-radius: 5px;
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
.auth-container button {
|
| 161 |
-
width: 100%;
|
| 162 |
-
padding: 12px;
|
| 163 |
-
background-color: #2ecc71;
|
| 164 |
-
color: white;
|
| 165 |
-
border: none;
|
| 166 |
-
border-radius: 5px;
|
| 167 |
-
cursor: pointer;
|
| 168 |
-
font-size: 1rem;
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
.auth-container p {
|
| 172 |
-
margin-top: 1rem;
|
| 173 |
-
}
|
| 174 |
-
.auth-container a {
|
| 175 |
-
color: #3498db;
|
| 176 |
-
text-decoration: none;
|
| 177 |
-
font-weight: bold;
|
| 178 |
-
}
|
| 179 |
-
.user-type {
|
| 180 |
-
margin-bottom: 1rem;
|
| 181 |
-
text-align: left;
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
/* Dashboard */
|
| 185 |
-
.dashboard-form {
|
| 186 |
-
background: white;
|
| 187 |
-
padding: 1.5rem;
|
| 188 |
-
border-radius: 8px;
|
| 189 |
-
margin-bottom: 2rem;
|
| 190 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 191 |
-
}
|
| 192 |
-
.dashboard-form input, .dashboard-form textarea {
|
| 193 |
-
width: calc(100% - 22px);
|
| 194 |
-
padding: 10px;
|
| 195 |
-
margin-bottom: 10px;
|
| 196 |
-
border: 1px solid #ccc;
|
| 197 |
-
border-radius: 4px;
|
| 198 |
-
}
|
| 199 |
-
.dashboard-form button {
|
| 200 |
-
background-color: #2c3e50;
|
| 201 |
-
color: white;
|
| 202 |
-
padding: 10px 20px;
|
| 203 |
-
border: none;
|
| 204 |
-
border-radius: 4px;
|
| 205 |
-
cursor: pointer;
|
| 206 |
-
}
|
| 207 |
-
|
| 208 |
-
/* AI Assistant */
|
| 209 |
-
.ai-assistant {
|
| 210 |
-
background-color: #eaf5ff;
|
| 211 |
-
border: 1px solid #bde0ff;
|
| 212 |
-
border-radius: 8px;
|
| 213 |
-
padding: 1.5rem;
|
| 214 |
-
margin-bottom: 2rem;
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
.ai-assistant h3 {
|
| 218 |
-
margin-top: 0;
|
| 219 |
-
}
|
| 220 |
-
|
| 221 |
-
.ai-chat-box {
|
| 222 |
-
display: flex;
|
| 223 |
-
gap: 10px;
|
| 224 |
-
}
|
| 225 |
-
|
| 226 |
-
.ai-chat-box input {
|
| 227 |
-
flex-grow: 1;
|
| 228 |
-
padding: 10px;
|
| 229 |
-
border: 1px solid #ccc;
|
| 230 |
-
border-radius: 4px;
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
.ai-chat-box button {
|
| 234 |
-
padding: 10px 20px;
|
| 235 |
-
background-color: #3498db;
|
| 236 |
-
color: white;
|
| 237 |
-
border: none;
|
| 238 |
-
border-radius: 4px;
|
| 239 |
-
cursor: pointer;
|
| 240 |
-
}
|
| 241 |
-
|
| 242 |
-
#ai-results {
|
| 243 |
-
margin-top: 1rem;
|
| 244 |
-
}</script>
|
| 245 |
-
</body>
|
| 246 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bun.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
cart.html
DELETED
|
@@ -1,653 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Shopping Cart - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<script src="js/cart.js"></script>
|
| 9 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#cffafe',
|
| 25 |
-
200: '#a5f3fc',
|
| 26 |
-
300: '#67e8f9',
|
| 27 |
-
400: '#00d4ff',
|
| 28 |
-
500: '#00d4ff',
|
| 29 |
-
600: '#0891b2',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
|
| 44 |
-
|
| 45 |
-
* {
|
| 46 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
.loading-spinner {
|
| 50 |
-
animation: spin 1s linear infinite;
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
@keyframes spin {
|
| 54 |
-
0% { transform: rotate(0deg); }
|
| 55 |
-
100% { transform: rotate(360deg); }
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
@keyframes fadeIn {
|
| 59 |
-
from { opacity: 0; transform: translateY(10px); }
|
| 60 |
-
to { opacity: 1; transform: translateY(0); }
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
@keyframes slideIn {
|
| 64 |
-
from { opacity: 0; transform: translateX(-20px); }
|
| 65 |
-
to { opacity: 1; transform: translateX(0); }
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
.fade-in {
|
| 69 |
-
animation: fadeIn 0.3s ease-out forwards;
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
.slide-in {
|
| 73 |
-
animation: slideIn 0.3s ease-out forwards;
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
.cart-item {
|
| 77 |
-
animation: fadeIn 0.3s ease-out forwards;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
.cart-item:nth-child(1) { animation-delay: 0.05s; }
|
| 81 |
-
.cart-item:nth-child(2) { animation-delay: 0.1s; }
|
| 82 |
-
.cart-item:nth-child(3) { animation-delay: 0.15s; }
|
| 83 |
-
.cart-item:nth-child(4) { animation-delay: 0.2s; }
|
| 84 |
-
.cart-item:nth-child(5) { animation-delay: 0.25s; }
|
| 85 |
-
|
| 86 |
-
.quantity-btn {
|
| 87 |
-
transition: all 0.2s ease;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
.quantity-btn:hover {
|
| 91 |
-
transform: scale(1.1);
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
.quantity-btn:active {
|
| 95 |
-
transform: scale(0.95);
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
.product-image {
|
| 99 |
-
transition: transform 0.3s ease;
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
.product-image:hover {
|
| 103 |
-
transform: scale(1.05);
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
.checkout-btn {
|
| 107 |
-
background: linear-gradient(135deg, #00d4ff 0%, #00d4ff 100%);
|
| 108 |
-
transition: all 0.3s ease;
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
.checkout-btn:hover {
|
| 112 |
-
transform: translateY(-2px);
|
| 113 |
-
box-shadow: 0 10px 30px rgba(245, 158, 11, 0.3);
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
.checkout-btn:active {
|
| 117 |
-
transform: translateY(0);
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
.summary-card {
|
| 121 |
-
background: linear-gradient(180deg, #ffffff 0%, #fafafa 100%);
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
.delete-btn {
|
| 125 |
-
transition: all 0.2s ease;
|
| 126 |
-
}
|
| 127 |
-
|
| 128 |
-
.delete-btn:hover {
|
| 129 |
-
background: #fee2e2;
|
| 130 |
-
color: #ef4444;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
.promo-input:focus {
|
| 134 |
-
box-shadow: 0 0 0 3px rgba(240, 71, 74, 0.1);
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
.skeleton {
|
| 138 |
-
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
| 139 |
-
background-size: 200% 100%;
|
| 140 |
-
animation: shimmer 1.5s infinite;
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
@keyframes shimmer {
|
| 144 |
-
0% { background-position: -200% 0; }
|
| 145 |
-
100% { background-position: 200% 0; }
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
.empty-cart-icon {
|
| 149 |
-
animation: float 3s ease-in-out infinite;
|
| 150 |
-
}
|
| 151 |
-
|
| 152 |
-
@keyframes float {
|
| 153 |
-
0%, 100% { transform: translateY(0); }
|
| 154 |
-
50% { transform: translateY(-10px); }
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
/* Custom scrollbar */
|
| 158 |
-
::-webkit-scrollbar {
|
| 159 |
-
width: 6px;
|
| 160 |
-
}
|
| 161 |
-
|
| 162 |
-
::-webkit-scrollbar-track {
|
| 163 |
-
background: #f1f1f1;
|
| 164 |
-
border-radius: 10px;
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
::-webkit-scrollbar-thumb {
|
| 168 |
-
background: #c1c1c1;
|
| 169 |
-
border-radius: 10px;
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
::-webkit-scrollbar-thumb:hover {
|
| 173 |
-
background: #a1a1a1;
|
| 174 |
-
}
|
| 175 |
-
</style>
|
| 176 |
-
</head>
|
| 177 |
-
<body class="bg-gray-50 min-h-screen">
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
<!-- Header -->
|
| 181 |
-
<header class="bg-white shadow-sm sticky top-0 z-50">
|
| 182 |
-
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 183 |
-
<div class="flex justify-between items-center h-16">
|
| 184 |
-
<a href="index.html" class="flex items-center gap-2 group">
|
| 185 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 186 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 187 |
-
<defs>
|
| 188 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 189 |
-
<stop offset="0%" stop-color="#00d4ff"/>
|
| 190 |
-
<stop offset="50%" stop-color="#00d4ff"/>
|
| 191 |
-
<stop offset="100%" stop-color="#0891b2"/>
|
| 192 |
-
</linearGradient>
|
| 193 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 194 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 195 |
-
<feMerge>
|
| 196 |
-
<feMergeNode in="coloredBlur"/>
|
| 197 |
-
<feMergeNode in="SourceGraphic"/>
|
| 198 |
-
</feMerge>
|
| 199 |
-
</filter>
|
| 200 |
-
</defs>
|
| 201 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 202 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 203 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 204 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 205 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 206 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 207 |
-
<g transform="translate(52, 8)">
|
| 208 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#00d4ff"/>
|
| 209 |
-
</g>
|
| 210 |
-
</svg>
|
| 211 |
-
</div>
|
| 212 |
-
<div class="flex flex-col">
|
| 213 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-cyan-400 to-cyan-700 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 214 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 215 |
-
</div>
|
| 216 |
-
</a>
|
| 217 |
-
<div class="flex items-center space-x-4">
|
| 218 |
-
<a href="index.html" class="text-gray-600 hover:text-cyan-500 transition-colors">
|
| 219 |
-
<i class="fas fa-home text-xl"></i>
|
| 220 |
-
</a>
|
| 221 |
-
<a href="cart.html" class="relative text-cyan-500">
|
| 222 |
-
<i class="fas fa-shopping-cart text-xl"></i>
|
| 223 |
-
<span id="cartCount" class="absolute -top-2 -right-2 bg-cyan-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 224 |
-
</a>
|
| 225 |
-
</div>
|
| 226 |
-
</div>
|
| 227 |
-
</nav>
|
| 228 |
-
</header>
|
| 229 |
-
|
| 230 |
-
<!-- Breadcrumb -->
|
| 231 |
-
<div class="bg-white border-b">
|
| 232 |
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
| 233 |
-
<nav class="flex items-center space-x-2 text-sm text-gray-500">
|
| 234 |
-
<a href="index.html" class="hover:text-cyan-500 transition-colors">Home</a>
|
| 235 |
-
<i class="fas fa-chevron-right text-xs"></i>
|
| 236 |
-
<span class="text-gray-900 font-medium">Shopping Cart</span>
|
| 237 |
-
</nav>
|
| 238 |
-
</div>
|
| 239 |
-
</div>
|
| 240 |
-
|
| 241 |
-
<!-- Main Content -->
|
| 242 |
-
<main class="max-w pb-20-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
| 243 |
-
<!-- Page Title -->
|
| 244 |
-
<div class="flex items-center justify-between mb-8">
|
| 245 |
-
<div>
|
| 246 |
-
<h1 class="text-3xl font-bold text-gray-900">Shopping Cart</h1>
|
| 247 |
-
<p class="text-gray-500 mt-1" id="cartSummary">Loading your cart...</p>
|
| 248 |
-
</div>
|
| 249 |
-
<a href="index.html" class="hidden sm:flex items-center text-cyan-500 hover:text-cyan-600 transition-colors font-medium">
|
| 250 |
-
<i class="fas fa-arrow-left mr-2"></i>
|
| 251 |
-
Continue Shopping
|
| 252 |
-
</a>
|
| 253 |
-
</div>
|
| 254 |
-
|
| 255 |
-
<!-- Cart Content -->
|
| 256 |
-
<div id="cartContent">
|
| 257 |
-
<!-- Loading Skeleton -->
|
| 258 |
-
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 259 |
-
<div class="lg:col-span-2 space-y-4">
|
| 260 |
-
<div class="bg-white rounded-2xl p-6 shadow-sm">
|
| 261 |
-
<div class="flex items-center space-x-4">
|
| 262 |
-
<div class="skeleton w-24 h-24 rounded-xl"></div>
|
| 263 |
-
<div class="flex-1 space-y-3">
|
| 264 |
-
<div class="skeleton h-4 w-3/4 rounded"></div>
|
| 265 |
-
<div class="skeleton h-3 w-1/2 rounded"></div>
|
| 266 |
-
<div class="skeleton h-6 w-1/4 rounded"></div>
|
| 267 |
-
</div>
|
| 268 |
-
</div>
|
| 269 |
-
</div>
|
| 270 |
-
<div class="bg-white rounded-2xl p-6 shadow-sm">
|
| 271 |
-
<div class="flex items-center space-x-4">
|
| 272 |
-
<div class="skeleton w-24 h-24 rounded-xl"></div>
|
| 273 |
-
<div class="flex-1 space-y-3">
|
| 274 |
-
<div class="skeleton h-4 w-3/4 rounded"></div>
|
| 275 |
-
<div class="skeleton h-3 w-1/2 rounded"></div>
|
| 276 |
-
<div class="skeleton h-6 w-1/4 rounded"></div>
|
| 277 |
-
</div>
|
| 278 |
-
</div>
|
| 279 |
-
</div>
|
| 280 |
-
</div>
|
| 281 |
-
<div class="lg:col-span-1">
|
| 282 |
-
<div class="bg-white rounded-2xl p-6 shadow-sm">
|
| 283 |
-
<div class="skeleton h-6 w-1/2 rounded mb-6"></div>
|
| 284 |
-
<div class="space-y-4">
|
| 285 |
-
<div class="skeleton h-4 w-full rounded"></div>
|
| 286 |
-
<div class="skeleton h-4 w-3/4 rounded"></div>
|
| 287 |
-
<div class="skeleton h-10 w-full rounded mt-6"></div>
|
| 288 |
-
</div>
|
| 289 |
-
</div>
|
| 290 |
-
</div>
|
| 291 |
-
</div>
|
| 292 |
-
</div>
|
| 293 |
-
</main>
|
| 294 |
-
|
| 295 |
-
<!-- Footer -->
|
| 296 |
-
<footer class="bg-dark-900 text-white py-8 mt-12">
|
| 297 |
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
| 298 |
-
<p>© 2025 Cellex. All rights reserved.</p>
|
| 299 |
-
</div>
|
| 300 |
-
</footer>
|
| 301 |
-
|
| 302 |
-
<script>
|
| 303 |
-
let currentUser = null;
|
| 304 |
-
|
| 305 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 306 |
-
await checkAuth();
|
| 307 |
-
await loadCart();
|
| 308 |
-
});
|
| 309 |
-
|
| 310 |
-
async function checkAuth() {
|
| 311 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 312 |
-
currentUser = user || null;
|
| 313 |
-
}
|
| 314 |
-
|
| 315 |
-
async function loadCart() {
|
| 316 |
-
const container = document.getElementById('cartContent');
|
| 317 |
-
|
| 318 |
-
// If not logged in, show login prompt
|
| 319 |
-
if (!currentUser) {
|
| 320 |
-
renderLoginPrompt();
|
| 321 |
-
return;
|
| 322 |
-
}
|
| 323 |
-
|
| 324 |
-
try {
|
| 325 |
-
const cartItems = await window.Cart.getCartItems();
|
| 326 |
-
|
| 327 |
-
if (!cartItems || cartItems.length === 0) {
|
| 328 |
-
renderEmptyCart();
|
| 329 |
-
return;
|
| 330 |
-
}
|
| 331 |
-
|
| 332 |
-
const cartTotal = cartItems.reduce((sum, item) => {
|
| 333 |
-
return sum + ((item.products?.price || 0) * (item.quantity || 1));
|
| 334 |
-
}, 0);
|
| 335 |
-
|
| 336 |
-
// Calculate total quantity for summary
|
| 337 |
-
const totalItems = cartItems.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
| 338 |
-
document.getElementById('cartSummary').textContent = `${totalItems} item${totalItems !== 1 ? 's' : ''} in your cart`;
|
| 339 |
-
|
| 340 |
-
container.innerHTML = `
|
| 341 |
-
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 342 |
-
<!-- Cart Items -->
|
| 343 |
-
<div class="lg:col-span-2 space-y-4">
|
| 344 |
-
${cartItems.map((item, index) => {
|
| 345 |
-
if (!item.products) return '';
|
| 346 |
-
return renderCartItem(item.products, item.quantity, index, item.id);
|
| 347 |
-
}).join('')}
|
| 348 |
-
|
| 349 |
-
<!-- Continue Shopping -->
|
| 350 |
-
<a href="index.html" class="flex items-center justify-center py-4 border-2 border-dashed border-gray-200 rounded-xl text-gray-500 hover:text-cyan-500 hover:border-brand-400 transition-colors">
|
| 351 |
-
<i class="fas fa-plus mr-2"></i>
|
| 352 |
-
Add more items
|
| 353 |
-
</a>
|
| 354 |
-
</div>
|
| 355 |
-
|
| 356 |
-
<!-- Order Summary -->
|
| 357 |
-
${renderOrderSummary(cartTotal, true)}
|
| 358 |
-
</div>
|
| 359 |
-
`;
|
| 360 |
-
|
| 361 |
-
// Update cart count in header
|
| 362 |
-
if (window.Cart) {
|
| 363 |
-
await window.Cart.updateCartCountUI();
|
| 364 |
-
}
|
| 365 |
-
} catch (error) {
|
| 366 |
-
console.error('Error loading cart:', error);
|
| 367 |
-
container.innerHTML = `
|
| 368 |
-
<div class="bg-white rounded-2xl p-12 text-center shadow-sm">
|
| 369 |
-
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
| 370 |
-
<i class="fas fa-exclamation-triangle text-2xl text-red-500"></i>
|
| 371 |
-
</div>
|
| 372 |
-
<h3 class="text-xl font-semibold text-gray-900 mb-2">Error Loading Cart</h3>
|
| 373 |
-
<p class="text-gray-500 mb-6">There was a problem loading your cart. Please try again.</p>
|
| 374 |
-
<button onclick="loadCart()" class="text-cyan-500 font-medium hover:underline">
|
| 375 |
-
<i class="fas fa-redo mr-2"></i>Try Again
|
| 376 |
-
</button>
|
| 377 |
-
</div>
|
| 378 |
-
`;
|
| 379 |
-
}
|
| 380 |
-
}
|
| 381 |
-
|
| 382 |
-
function renderLoginPrompt() {
|
| 383 |
-
document.getElementById('cartSummary').textContent = 'Please login to view your cart';
|
| 384 |
-
document.getElementById('cartContent').innerHTML = `
|
| 385 |
-
<div class="bg-white rounded-2xl p-12 text-center shadow-sm max-w-2xl mx-auto fade-in">
|
| 386 |
-
<div class="w-32 h-32 bg-brand-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
| 387 |
-
<i class="fas fa-user-lock text-5xl text-cyan-400"></i>
|
| 388 |
-
</div>
|
| 389 |
-
<h2 class="text-2xl font-bold text-gray-900 mb-2">Login Required</h2>
|
| 390 |
-
<p class="text-gray-500 mb-8">Please login to view and manage your shopping cart. Your cart will be saved across all your devices.</p>
|
| 391 |
-
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
| 392 |
-
<a href="login.html?next=cart.html" class="px-8 py-3 bg-cyan-500 text-white font-semibold rounded-xl hover:bg-cyan-600 transition-colors">
|
| 393 |
-
<i class="fas fa-sign-in-alt mr-2"></i>Login
|
| 394 |
-
</a>
|
| 395 |
-
<a href="signup.html?next=cart.html" class="px-8 py-3 border-2 border-cyan-500 text-cyan-500 font-semibold rounded-xl hover:bg-brand-50 transition-colors">
|
| 396 |
-
<i class="fas fa-user-plus mr-2"></i>Create Account
|
| 397 |
-
</a>
|
| 398 |
-
</div>
|
| 399 |
-
</div>
|
| 400 |
-
`;
|
| 401 |
-
}
|
| 402 |
-
|
| 403 |
-
function renderCartItem(product, quantity, index, cartItemId = null) {
|
| 404 |
-
const discount = product.original_price ? Math.round(((product.original_price - product.price) / product.original_price) * 100) : 0;
|
| 405 |
-
const image = product.image || product.image_url || 'https://via.placeholder.com/200';
|
| 406 |
-
const productId = product.id;
|
| 407 |
-
|
| 408 |
-
return `
|
| 409 |
-
<div class="cart-item bg-white rounded-2xl p-4 sm:p-6 shadow-sm hover:shadow-md transition-shadow" data-product-id="${productId}" data-cart-id="${cartItemId || ''}">
|
| 410 |
-
<div class="flex items-start gap-4">
|
| 411 |
-
<!-- Product Image -->
|
| 412 |
-
<div class="flex-shrink-0">
|
| 413 |
-
<a href="product.html?id=${productId}">
|
| 414 |
-
<img src="${image}" alt="${product.name || 'Product'}"
|
| 415 |
-
class="product-image w-24 h-24 sm:w-32 sm:h-32 rounded-xl object-cover cursor-pointer">
|
| 416 |
-
</a>
|
| 417 |
-
</div>
|
| 418 |
-
|
| 419 |
-
<!-- Product Details -->
|
| 420 |
-
<div class="flex-1 min-w-0">
|
| 421 |
-
<div class="flex items-start justify-between gap-2">
|
| 422 |
-
<div>
|
| 423 |
-
<a href="product.html?id=${productId}" class="text-gray-400 text-xs uppercase tracking-wide">${product.category || 'Category'}</a>
|
| 424 |
-
<h3 class="text-lg font-semibold text-gray-900 mt-1 hover:text-cyan-500 transition-colors">
|
| 425 |
-
<a href="product.html?id=${productId}">${product.name || 'Product Name'}</a>
|
| 426 |
-
</h3>
|
| 427 |
-
</div>
|
| 428 |
-
<button onclick="removeItem('${productId}', '${cartItemId}')"
|
| 429 |
-
class="delete-btn p-2 rounded-lg text-gray-400 hover:text-red-500">
|
| 430 |
-
<i class="fas fa-trash-alt"></i>
|
| 431 |
-
</button>
|
| 432 |
-
</div>
|
| 433 |
-
|
| 434 |
-
<!-- Price & Discount -->
|
| 435 |
-
<div class="flex items-center gap-3 mt-2">
|
| 436 |
-
<span class="text-xl font-bold text-gray-900">₦${(product.price || 0).toFixed(2)}</span>
|
| 437 |
-
${product.original_price ? `
|
| 438 |
-
<span class="text-sm text-gray-400 line-through">₦${product.original_price.toFixed(2)}</span>
|
| 439 |
-
<span class="bg-red-100 text-red-600 text-xs font-semibold px-2 py-1 rounded-full">-${discount}%</span>
|
| 440 |
-
` : ''}
|
| 441 |
-
</div>
|
| 442 |
-
|
| 443 |
-
<!-- Quantity Controls -->
|
| 444 |
-
<div class="flex items-center justify-between mt-4">
|
| 445 |
-
<div class="flex items-center bg-gray-100 rounded-xl p-1">
|
| 446 |
-
<button onclick="updateQuantity('${productId}', ${quantity - 1}, '${cartItemId}')"
|
| 447 |
-
class="quantity-btn w-10 h-10 flex items-center justify-center rounded-lg bg-white shadow-sm hover:bg-gray-50 ${quantity <= 1 ? 'opacity-50 cursor-not-allowed' : ''}">
|
| 448 |
-
<i class="fas fa-minus text-gray-600"></i>
|
| 449 |
-
</button>
|
| 450 |
-
<span class="w-14 text-center font-semibold text-gray-900">${quantity}</span>
|
| 451 |
-
<button onclick="updateQuantity('${productId}', ${quantity + 1}, '${cartItemId}')"
|
| 452 |
-
class="quantity-btn w-10 h-10 flex items-center justify-center rounded-lg bg-white shadow-sm hover:bg-gray-50">
|
| 453 |
-
<i class="fas fa-plus text-gray-600"></i>
|
| 454 |
-
</button>
|
| 455 |
-
</div>
|
| 456 |
-
|
| 457 |
-
<div class="text-right">
|
| 458 |
-
<span class="text-sm text-gray-500">Subtotal</span>
|
| 459 |
-
<p class="text-lg font-bold text-cyan-500">₦${((product.price || 0) * quantity).toFixed(2)}</p>
|
| 460 |
-
</div>
|
| 461 |
-
</div>
|
| 462 |
-
</div>
|
| 463 |
-
</div>
|
| 464 |
-
</div>
|
| 465 |
-
`;
|
| 466 |
-
}
|
| 467 |
-
|
| 468 |
-
function renderOrderSummary(cartTotal, isLoggedIn) {
|
| 469 |
-
const shipping = cartTotal > 100 ? 0 : (cartTotal > 0 ? 10.00 : 0);
|
| 470 |
-
const tax = cartTotal * 0.1;
|
| 471 |
-
const total = cartTotal + shipping + tax;
|
| 472 |
-
|
| 473 |
-
return `
|
| 474 |
-
<div class="lg:col-span-1">
|
| 475 |
-
<div class="summary-card bg-white rounded-2xl shadow-sm p-6 sticky top-24">
|
| 476 |
-
<h2 class="text-xl font-bold text-gray-900 mb-6">Order Summary</h2>
|
| 477 |
-
|
| 478 |
-
<!-- Promo Code -->
|
| 479 |
-
<div class="mb-6">
|
| 480 |
-
<label class="text-sm font-medium text-gray-700 mb-2 block">Promo Code</label>
|
| 481 |
-
<div class="flex gap-2">
|
| 482 |
-
<input type="text" placeholder="Enter code"
|
| 483 |
-
class="promo-input flex-1 px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 transition-colors">
|
| 484 |
-
<button class="px-4 py-3 bg-dark-900 text-white rounded-xl font-medium hover:bg-dark-800 transition-colors">
|
| 485 |
-
Apply
|
| 486 |
-
</button>
|
| 487 |
-
</div>
|
| 488 |
-
</div>
|
| 489 |
-
|
| 490 |
-
<div class="space-y-4 border-t pt-4">
|
| 491 |
-
<div class="flex justify-between text-gray-600">
|
| 492 |
-
<span>Subtotal</span>
|
| 493 |
-
<span class="font-medium">₦${cartTotal.toFixed(2)}</span>
|
| 494 |
-
</div>
|
| 495 |
-
<div class="flex justify-between text-gray-600">
|
| 496 |
-
<span>Shipping</span>
|
| 497 |
-
<span class="font-medium">${shipping > 0 ? '$' + shipping.toFixed(2) : 'Free'}</span>
|
| 498 |
-
</div>
|
| 499 |
-
<div class="flex justify-between text-gray-600">
|
| 500 |
-
<span>Tax (10%)</span>
|
| 501 |
-
<span class="font-medium">₦${tax.toFixed(2)}</span>
|
| 502 |
-
</div>
|
| 503 |
-
|
| 504 |
-
${cartTotal > 100 ? `
|
| 505 |
-
<div class="bg-green-50 border border-green-200 rounded-xl p-3 text-sm text-green-700">
|
| 506 |
-
<i class="fas fa-check-circle mr-2"></i>
|
| 507 |
-
You're saving ₦10.00 on shipping!
|
| 508 |
-
</div>
|
| 509 |
-
` : ''}
|
| 510 |
-
</div>
|
| 511 |
-
|
| 512 |
-
<div class="border-t mt-4 pt-4">
|
| 513 |
-
<div class="flex justify-between items-center mb-6">
|
| 514 |
-
<span class="text-lg font-bold text-gray-900">Total</span>
|
| 515 |
-
<span class="text-2xl font-bold text-cyan-500">₦${total.toFixed(2)}</span>
|
| 516 |
-
</div>
|
| 517 |
-
|
| 518 |
-
<button onclick="proceedToCheckout()"
|
| 519 |
-
class="checkout-btn w-full py-4 text-white font-semibold rounded-xl flex items-center justify-center gap-2">
|
| 520 |
-
<i class="fas fa-lock"></i>
|
| 521 |
-
Proceed to Checkout
|
| 522 |
-
</button>
|
| 523 |
-
|
| 524 |
-
<p class="text-xs text-gray-500 text-center mt-4">
|
| 525 |
-
<i class="fas fa-shield-alt mr-1"></i>
|
| 526 |
-
Secure checkout powered by Stripe
|
| 527 |
-
</p>
|
| 528 |
-
</div>
|
| 529 |
-
|
| 530 |
-
<!-- Payment Methods -->
|
| 531 |
-
<div class="mt-6 pt-6 border-t">
|
| 532 |
-
<p class="text-xs text-gray-500 text-center mb-3">We accept</p>
|
| 533 |
-
<div class="flex justify-center gap-3 text-2xl text-gray-400">
|
| 534 |
-
<i class="fab fa-cc-visa"></i>
|
| 535 |
-
<i class="fab fa-cc-mastercard"></i>
|
| 536 |
-
<i class="fab fa-cc-amex"></i>
|
| 537 |
-
<i class="fab fa-cc-paypal"></i>
|
| 538 |
-
</div>
|
| 539 |
-
</div>
|
| 540 |
-
</div>
|
| 541 |
-
</div>
|
| 542 |
-
`;
|
| 543 |
-
}
|
| 544 |
-
|
| 545 |
-
function renderEmptyCart() {
|
| 546 |
-
document.getElementById('cartSummary').textContent = 'Your cart is empty';
|
| 547 |
-
|
| 548 |
-
document.getElementById('cartContent').innerHTML = `
|
| 549 |
-
<div class="bg-white rounded-2xl p-12 text-center shadow-sm max-w-2xl mx-auto fade-in">
|
| 550 |
-
<div class="empty-cart-icon w-32 h-32 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
| 551 |
-
<i class="fas fa-shopping-cart text-5xl text-gray-300"></i>
|
| 552 |
-
</div>
|
| 553 |
-
<h2 class="text-2xl font-bold text-gray-900 mb-2">Your cart is empty</h2>
|
| 554 |
-
<p class="text-gray-500 mb-8">Looks like you haven't added any items to your cart yet. Start shopping to fill it up!</p>
|
| 555 |
-
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
| 556 |
-
<a href="index.html" class="px-8 py-3 bg-cyan-500 text-white font-semibold rounded-xl hover:bg-cyan-600 transition-colors">
|
| 557 |
-
<i class="fas fa-shopping-bag mr-2"></i>Start Shopping
|
| 558 |
-
</a>
|
| 559 |
-
</div>
|
| 560 |
-
</div>
|
| 561 |
-
`;
|
| 562 |
-
}
|
| 563 |
-
|
| 564 |
-
async function updateQuantity(productId, newQuantity, cartItemId) {
|
| 565 |
-
if (newQuantity < 1) {
|
| 566 |
-
removeItem(productId, cartItemId);
|
| 567 |
-
return;
|
| 568 |
-
}
|
| 569 |
-
|
| 570 |
-
if (!currentUser) {
|
| 571 |
-
window.location.href = 'login.html?next=cart.html';
|
| 572 |
-
return;
|
| 573 |
-
}
|
| 574 |
-
|
| 575 |
-
try {
|
| 576 |
-
const result = await window.Cart.updateQuantity(cartItemId, newQuantity);
|
| 577 |
-
|
| 578 |
-
if (!result.success) throw new Error(result.message || 'Failed to update quantity');
|
| 579 |
-
|
| 580 |
-
await loadCart();
|
| 581 |
-
} catch (error) {
|
| 582 |
-
console.error('Error updating quantity:', error);
|
| 583 |
-
alert('Failed to update quantity. Please try again.');
|
| 584 |
-
}
|
| 585 |
-
}
|
| 586 |
-
|
| 587 |
-
async function removeItem(productId, cartItemId) {
|
| 588 |
-
if (!confirm('Remove this item from your cart?')) return;
|
| 589 |
-
|
| 590 |
-
if (!currentUser) {
|
| 591 |
-
window.location.href = 'login.html?next=cart.html';
|
| 592 |
-
return;
|
| 593 |
-
}
|
| 594 |
-
|
| 595 |
-
try {
|
| 596 |
-
const result = await window.Cart.removeFromCart(cartItemId);
|
| 597 |
-
|
| 598 |
-
if (!result.success) throw new Error(result.message || 'Failed to remove item');
|
| 599 |
-
|
| 600 |
-
await loadCart();
|
| 601 |
-
} catch (error) {
|
| 602 |
-
console.error('Error removing item:', error);
|
| 603 |
-
alert('Failed to remove item. Please try again.');
|
| 604 |
-
}
|
| 605 |
-
}
|
| 606 |
-
|
| 607 |
-
async function proceedToCheckout() {
|
| 608 |
-
if (!currentUser) {
|
| 609 |
-
window.location.href = 'login.html?next=cart.html';
|
| 610 |
-
return;
|
| 611 |
-
}
|
| 612 |
-
|
| 613 |
-
try {
|
| 614 |
-
const cartItems = await window.Cart.getCartItems();
|
| 615 |
-
|
| 616 |
-
if (!cartItems || cartItems.length === 0) {
|
| 617 |
-
alert('Your cart is empty!');
|
| 618 |
-
return;
|
| 619 |
-
}
|
| 620 |
-
|
| 621 |
-
const items = cartItems.map(item => ({
|
| 622 |
-
product_id: item.product_id,
|
| 623 |
-
name: item.products?.name || '',
|
| 624 |
-
quantity: item.quantity,
|
| 625 |
-
price: item.products?.price || 0,
|
| 626 |
-
total: (item.products?.price || 0) * item.quantity
|
| 627 |
-
}));
|
| 628 |
-
|
| 629 |
-
const cartTotal = items.reduce((sum, item) => sum + item.total, 0);
|
| 630 |
-
const shipping = cartTotal > 100 ? 0 : 10.00;
|
| 631 |
-
const tax = cartTotal * 0.1;
|
| 632 |
-
const total = cartTotal + shipping + tax;
|
| 633 |
-
|
| 634 |
-
// Store in sessionStorage for checkout page (cleared after checkout)
|
| 635 |
-
sessionStorage.setItem('checkoutItems', JSON.stringify({
|
| 636 |
-
items,
|
| 637 |
-
subtotal: cartTotal,
|
| 638 |
-
shipping,
|
| 639 |
-
tax,
|
| 640 |
-
total,
|
| 641 |
-
userId: currentUser.id
|
| 642 |
-
}));
|
| 643 |
-
|
| 644 |
-
window.location.href = 'checkout.html';
|
| 645 |
-
|
| 646 |
-
} catch (error) {
|
| 647 |
-
console.error('Error proceeding to checkout:', error);
|
| 648 |
-
alert('There was an error processing your cart. Please try again.');
|
| 649 |
-
}
|
| 650 |
-
}
|
| 651 |
-
</script>
|
| 652 |
-
</body>
|
| 653 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
categories.html
DELETED
|
@@ -1,611 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Categories — Cellex</title>
|
| 7 |
-
<meta name="description" content="Browse all categories on Cellex - Nigeria's #1 Marketplace">
|
| 8 |
-
|
| 9 |
-
<!-- Fonts & Icons -->
|
| 10 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 11 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 12 |
-
|
| 13 |
-
<!-- Tailwind & Supabase -->
|
| 14 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 15 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 16 |
-
<script src="js/config/edge-functions.js?v=3"></script>
|
| 17 |
-
<script src="js/cart.js?v=11"></script>
|
| 18 |
-
|
| 19 |
-
<script>
|
| 20 |
-
tailwind.config = {
|
| 21 |
-
theme: {
|
| 22 |
-
extend: {
|
| 23 |
-
fontFamily: {
|
| 24 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 25 |
-
},
|
| 26 |
-
colors: {
|
| 27 |
-
brand: {
|
| 28 |
-
50: '#fffbeb',
|
| 29 |
-
100: '#cffafe',
|
| 30 |
-
200: '#a5f3fc',
|
| 31 |
-
300: '#67e8f9',
|
| 32 |
-
400: '#00d4ff',
|
| 33 |
-
500: '#00d4ff',
|
| 34 |
-
600: '#0891b2',
|
| 35 |
-
700: '#b45309',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
|
| 43 |
-
<style>
|
| 44 |
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 45 |
-
html { scroll-behavior: smooth; }
|
| 46 |
-
body {
|
| 47 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 48 |
-
background: #ffffff;
|
| 49 |
-
color: #1f2937;
|
| 50 |
-
}
|
| 51 |
-
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 52 |
-
::-webkit-scrollbar-track { background: #f5f5f5; }
|
| 53 |
-
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
|
| 54 |
-
::-webkit-scrollbar-thumb:hover { background: #9ca3af; }
|
| 55 |
-
|
| 56 |
-
.glass {
|
| 57 |
-
background: rgba(255, 255, 255, 0.95);
|
| 58 |
-
backdrop-filter: blur(20px);
|
| 59 |
-
-webkit-backdrop-filter: blur(20px);
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
.category-card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0,0,0,0.1); }
|
| 63 |
-
.category-card { transition: all 0.3s ease; }
|
| 64 |
-
|
| 65 |
-
.product-card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0,0,0,0.1); }
|
| 66 |
-
.product-card { transition: all 0.3s ease; }
|
| 67 |
-
|
| 68 |
-
.filter-btn.active { background: #00d4ff; color: white; border-color: #00d4ff; }
|
| 69 |
-
</style>
|
| 70 |
-
</head>
|
| 71 |
-
|
| 72 |
-
<body class="min-h-screen pb-20 lg:pb-0">
|
| 73 |
-
|
| 74 |
-
<!-- Universal Navbar -->
|
| 75 |
-
<div id="nav-mount"></div>
|
| 76 |
-
<script src="js/load-navbar.js"></script>
|
| 77 |
-
|
| 78 |
-
<!-- Header -->
|
| 79 |
-
|
| 80 |
-
<!-- Main Content -->
|
| 81 |
-
<main class="max-w pb-20-7xl mx-auto px-3 lg:px-4 py-4 lg:py-6">
|
| 82 |
-
<!-- Page Title -->
|
| 83 |
-
<div class="mb-4 lg:mb-6">
|
| 84 |
-
<h1 class="text-xl lg:text-2xl font-bold text-gray-900">All Categories</h1>
|
| 85 |
-
<p class="text-gray-500 text-xs lg:text-sm mt-1">Browse products by category</p>
|
| 86 |
-
</div>
|
| 87 |
-
|
| 88 |
-
<!-- Categories Grid -->
|
| 89 |
-
<section class="mb-6 lg:mb-8">
|
| 90 |
-
<div class="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-6 gap-2 lg:gap-4" id="categoriesGrid">
|
| 91 |
-
<!-- All Categories -->
|
| 92 |
-
<button onclick="filterByCategory('')" class="category-card filter-btn active bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 93 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 94 |
-
<i class="fas fa-th-large text-gray-600 text-lg lg:text-xl"></i>
|
| 95 |
-
</div>
|
| 96 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">All</p>
|
| 97 |
-
</button>
|
| 98 |
-
<!-- Phones -->
|
| 99 |
-
<button onclick="filterByCategory('phones')" data-category="phones" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 100 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 101 |
-
<i class="fas fa-mobile-alt text-gray-600 text-lg lg:text-xl"></i>
|
| 102 |
-
</div>
|
| 103 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Phones</p>
|
| 104 |
-
</button>
|
| 105 |
-
<!-- Electronics -->
|
| 106 |
-
<button onclick="filterByCategory('electronics')" data-category="electronics" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 107 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 108 |
-
<i class="fas fa-laptop text-gray-600 text-lg lg:text-xl"></i>
|
| 109 |
-
</div>
|
| 110 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Electronics</p>
|
| 111 |
-
</button>
|
| 112 |
-
<!-- Fashion -->
|
| 113 |
-
<button onclick="filterByCategory('fashion')" data-category="fashion" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 114 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 115 |
-
<i class="fas fa-tshirt text-gray-600 text-lg lg:text-xl"></i>
|
| 116 |
-
</div>
|
| 117 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Fashion</p>
|
| 118 |
-
</button>
|
| 119 |
-
<!-- Farm Fresh -->
|
| 120 |
-
<button onclick="filterByCategory('farm')" data-category="farm" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 121 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 122 |
-
<i class="fas fa-leaf text-gray-600 text-lg lg:text-xl"></i>
|
| 123 |
-
</div>
|
| 124 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Farm Fresh</p>
|
| 125 |
-
</button>
|
| 126 |
-
<!-- Home -->
|
| 127 |
-
<button onclick="filterByCategory('home')" data-category="home" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 128 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 129 |
-
<i class="fas fa-home text-gray-600 text-lg lg:text-xl"></i>
|
| 130 |
-
</div>
|
| 131 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Home</p>
|
| 132 |
-
</button>
|
| 133 |
-
<!-- Beauty -->
|
| 134 |
-
<button onclick="filterByCategory('beauty')" data-category="beauty" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 135 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 136 |
-
<i class="fas fa-heart text-gray-600 text-lg lg:text-xl"></i>
|
| 137 |
-
</div>
|
| 138 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Beauty</p>
|
| 139 |
-
</button>
|
| 140 |
-
<!-- Baby -->
|
| 141 |
-
<button onclick="filterByCategory('baby')" data-category="baby" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 142 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 143 |
-
<i class="fas fa-baby text-gray-600 text-lg lg:text-xl"></i>
|
| 144 |
-
</div>
|
| 145 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Baby</p>
|
| 146 |
-
</button>
|
| 147 |
-
<!-- Sports -->
|
| 148 |
-
<button onclick="filterByCategory('sports')" data-category="sports" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 149 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 150 |
-
<i class="fas fa-futbol text-gray-600 text-lg lg:text-xl"></i>
|
| 151 |
-
</div>
|
| 152 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Sports</p>
|
| 153 |
-
</button>
|
| 154 |
-
<!-- Groceries -->
|
| 155 |
-
<button onclick="filterByCategory('groceries')" data-category="groceries" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 156 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 157 |
-
<i class="fas fa-shopping-basket text-gray-600 text-lg lg:text-xl"></i>
|
| 158 |
-
</div>
|
| 159 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Groceries</p>
|
| 160 |
-
</button>
|
| 161 |
-
<!-- Health -->
|
| 162 |
-
<button onclick="filterByCategory('health')" data-category="health" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 163 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 164 |
-
<i class="fas fa-pills text-gray-600 text-lg lg:text-xl"></i>
|
| 165 |
-
</div>
|
| 166 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Health</p>
|
| 167 |
-
</button>
|
| 168 |
-
<!-- Automotive -->
|
| 169 |
-
<button onclick="filterByCategory('automotive')" data-category="automotive" class="category-card filter-btn bg-white rounded-xl p-3 lg:p-4 text-center border border-gray-200 cursor-pointer">
|
| 170 |
-
<div class="w-10 h-10 lg:w-14 lg:h-14 mx-auto bg-gray-100 rounded-xl flex items-center justify-center mb-2 lg:mb-3">
|
| 171 |
-
<i class="fas fa-car text-gray-600 text-lg lg:text-xl"></i>
|
| 172 |
-
</div>
|
| 173 |
-
<p class="font-medium text-gray-700 text-xs lg:text-sm">Automotive</p>
|
| 174 |
-
</button>
|
| 175 |
-
</div>
|
| 176 |
-
</section>
|
| 177 |
-
|
| 178 |
-
<!-- Filters Bar -->
|
| 179 |
-
<section class="bg-gray-50 rounded-xl p-3 lg:p-4 mb-4 lg:mb-6">
|
| 180 |
-
<div class="flex flex-wrap items-center gap-2 lg:gap-4">
|
| 181 |
-
<!-- Sort -->
|
| 182 |
-
<div class="flex items-center gap-2">
|
| 183 |
-
<span class="text-xs lg:text-sm text-gray-500">Sort:</span>
|
| 184 |
-
<select id="sortSelect" onchange="applyFilters()" class="text-xs lg:text-sm border border-gray-200 rounded-lg px-2 lg:px-3 py-1.5 lg:py-2 bg-white">
|
| 185 |
-
<option value="newest">Newest</option>
|
| 186 |
-
<option value="popular">Popular</option>
|
| 187 |
-
<option value="price-low">Price: Low to High</option>
|
| 188 |
-
<option value="price-high">Price: High to Low</option>
|
| 189 |
-
</select>
|
| 190 |
-
</div>
|
| 191 |
-
<!-- Price Range -->
|
| 192 |
-
<div class="flex items-center gap-2">
|
| 193 |
-
<span class="text-xs lg:text-sm text-gray-500">Price:</span>
|
| 194 |
-
<select id="priceSelect" onchange="applyFilters()" class="text-xs lg:text-sm border border-gray-200 rounded-lg px-2 lg:px-3 py-1.5 lg:py-2 bg-white">
|
| 195 |
-
<option value="">All Prices</option>
|
| 196 |
-
<option value="0-5000">Under ₦5,000</option>
|
| 197 |
-
<option value="5000-10000">₦5,000 - ₦10,000</option>
|
| 198 |
-
<option value="10000-50000">₦10,000 - ₦50,000</option>
|
| 199 |
-
<option value="50000+">₦50,000+</option>
|
| 200 |
-
</select>
|
| 201 |
-
</div>
|
| 202 |
-
<!-- Seller Type -->
|
| 203 |
-
<div class="flex items-center gap-2">
|
| 204 |
-
<span class="text-xs lg:text-sm text-gray-500">Seller:</span>
|
| 205 |
-
<select id="sellerSelect" onchange="applyFilters()" class="text-xs lg:text-sm border border-gray-200 rounded-lg px-2 lg:px-3 py-1.5 lg:py-2 bg-white">
|
| 206 |
-
<option value="">All Sellers</option>
|
| 207 |
-
<option value="merchant">Merchants</option>
|
| 208 |
-
<option value="farmer">Farmers</option>
|
| 209 |
-
</select>
|
| 210 |
-
</div>
|
| 211 |
-
<!-- Results Count -->
|
| 212 |
-
<div class="ml-auto text-xs lg:text-sm text-gray-500">
|
| 213 |
-
<span id="resultsCount">0</span> products found
|
| 214 |
-
</div>
|
| 215 |
-
</div>
|
| 216 |
-
</section>
|
| 217 |
-
|
| 218 |
-
<!-- Products Grid -->
|
| 219 |
-
<section>
|
| 220 |
-
<div id="productsGrid" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2 lg:gap-4">
|
| 221 |
-
<!-- Products loaded dynamically -->
|
| 222 |
-
</div>
|
| 223 |
-
|
| 224 |
-
<!-- Loading State -->
|
| 225 |
-
<div id="loadingState" class="text-center py-12 hidden">
|
| 226 |
-
<i class="fas fa-spinner fa-spin text-cyan-500 text-2xl mb-3"></i>
|
| 227 |
-
<p class="text-gray-500 text-sm">Loading products...</p>
|
| 228 |
-
</div>
|
| 229 |
-
|
| 230 |
-
<!-- Empty State -->
|
| 231 |
-
<div id="emptyState" class="text-center py-12 hidden">
|
| 232 |
-
<i class="fas fa-box-open text-gray-300 text-4xl mb-3"></i>
|
| 233 |
-
<p class="text-gray-500 text-sm">No products found in this category</p>
|
| 234 |
-
<button onclick="filterByCategory('')" class="mt-3 text-cyan-500 text-sm font-medium hover:underline">View all products</button>
|
| 235 |
-
</div>
|
| 236 |
-
|
| 237 |
-
<!-- Load More -->
|
| 238 |
-
<div id="loadMoreSection" class="text-center mt-6 lg:mt-8 hidden">
|
| 239 |
-
<button onclick="loadMoreProducts()" class="bg-gray-100 text-gray-700 px-6 py-2 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
|
| 240 |
-
Load More Products
|
| 241 |
-
</button>
|
| 242 |
-
</div>
|
| 243 |
-
</section>
|
| 244 |
-
</main>
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
<!-- Auth Modal -->
|
| 248 |
-
<div id="authModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden items-center justify-center p-4">
|
| 249 |
-
<div class="bg-white rounded-2xl max-w-sm w-full p-6 text-center">
|
| 250 |
-
<div class="w-16 h-16 bg-cyan-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
| 251 |
-
<i class="fas fa-user-lock text-cyan-500 text-2xl"></i>
|
| 252 |
-
</div>
|
| 253 |
-
<h3 class="text-lg font-bold text-gray-900 mb-2">Sign In Required</h3>
|
| 254 |
-
<p class="text-gray-500 text-sm mb-4">Please log in to add items to your cart</p>
|
| 255 |
-
<div class="flex gap-3">
|
| 256 |
-
<a href="login.html" class="flex-1 bg-gray-900 text-white py-2.5 rounded-lg font-medium text-sm hover:bg-gray-800 transition-colors">
|
| 257 |
-
Login
|
| 258 |
-
</a>
|
| 259 |
-
<a href="signup.html" class="flex-1 bg-orange-500 text-white py-2.5 rounded-lg font-medium text-sm hover:bg-orange-600 transition-colors">
|
| 260 |
-
Sign Up
|
| 261 |
-
</a>
|
| 262 |
-
</div>
|
| 263 |
-
<button onclick="closeAuthModal()" class="mt-3 text-gray-500 text-sm hover:text-gray-700">Continue Browsing</button>
|
| 264 |
-
</div>
|
| 265 |
-
</div>
|
| 266 |
-
|
| 267 |
-
<!-- Scripts -->
|
| 268 |
-
<script>
|
| 269 |
-
// Edge function client + Cart module are loaded via <script> tags above.
|
| 270 |
-
// Use window.EdgeFunctions and window.Cart instead of supabaseClient.
|
| 271 |
-
let currentCategory = '';
|
| 272 |
-
let currentSearch = '';
|
| 273 |
-
let currentSort = 'newest';
|
| 274 |
-
let currentPrice = '';
|
| 275 |
-
let currentSeller = '';
|
| 276 |
-
let products = [];
|
| 277 |
-
let page = 0;
|
| 278 |
-
const pageSize = 20;
|
| 279 |
-
|
| 280 |
-
// Initialize
|
| 281 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 282 |
-
checkAuthState();
|
| 283 |
-
|
| 284 |
-
// Get parameters from URL
|
| 285 |
-
const params = new URLSearchParams(window.location.search);
|
| 286 |
-
const cat = params.get('category');
|
| 287 |
-
const search = params.get('search');
|
| 288 |
-
|
| 289 |
-
// Set search input value if search param exists
|
| 290 |
-
if (search) {
|
| 291 |
-
currentSearch = search;
|
| 292 |
-
const searchInput = document.getElementById('searchInput');
|
| 293 |
-
if (searchInput) searchInput.value = search;
|
| 294 |
-
updatePageTitle(search, '');
|
| 295 |
-
}
|
| 296 |
-
|
| 297 |
-
if (cat) {
|
| 298 |
-
filterByCategory(cat);
|
| 299 |
-
} else {
|
| 300 |
-
loadProducts();
|
| 301 |
-
}
|
| 302 |
-
});
|
| 303 |
-
|
| 304 |
-
// Update page title based on filters
|
| 305 |
-
function updatePageTitle(search, category) {
|
| 306 |
-
const titleEl = document.querySelector('main h1');
|
| 307 |
-
const descEl = document.querySelector('main h1 + p');
|
| 308 |
-
|
| 309 |
-
if (search && category) {
|
| 310 |
-
titleEl.textContent = `"${search}" in ${category}`;
|
| 311 |
-
descEl.textContent = 'Search results';
|
| 312 |
-
} else if (search) {
|
| 313 |
-
titleEl.textContent = `Search: "${search}"`;
|
| 314 |
-
descEl.textContent = 'Showing search results';
|
| 315 |
-
} else if (category) {
|
| 316 |
-
titleEl.textContent = category.charAt(0).toUpperCase() + category.slice(1);
|
| 317 |
-
descEl.textContent = 'Browse products in this category';
|
| 318 |
-
} else {
|
| 319 |
-
titleEl.textContent = 'All Categories';
|
| 320 |
-
descEl.textContent = 'Browse products by category';
|
| 321 |
-
}
|
| 322 |
-
}
|
| 323 |
-
|
| 324 |
-
// Auth State
|
| 325 |
-
async function checkAuthState() {
|
| 326 |
-
// Auth state is handled by the shared navbar
|
| 327 |
-
try {
|
| 328 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 329 |
-
window.currentUser = user;
|
| 330 |
-
} catch (error) {
|
| 331 |
-
console.error('Auth check error:', error);
|
| 332 |
-
}
|
| 333 |
-
}
|
| 334 |
-
|
| 335 |
-
// Filter by Category
|
| 336 |
-
function filterByCategory(category) {
|
| 337 |
-
currentCategory = category;
|
| 338 |
-
page = 0;
|
| 339 |
-
products = [];
|
| 340 |
-
|
| 341 |
-
// Clear search when changing category
|
| 342 |
-
currentSearch = '';
|
| 343 |
-
const searchInput = document.getElementById('searchInput');
|
| 344 |
-
if (searchInput) searchInput.value = '';
|
| 345 |
-
|
| 346 |
-
// Update active button
|
| 347 |
-
document.querySelectorAll('.filter-btn').forEach(btn => {
|
| 348 |
-
btn.classList.remove('active');
|
| 349 |
-
if (btn.dataset.category === category || (category === '' && !btn.dataset.category)) {
|
| 350 |
-
btn.classList.add('active');
|
| 351 |
-
}
|
| 352 |
-
});
|
| 353 |
-
|
| 354 |
-
// Update URL
|
| 355 |
-
const url = new URL(window.location);
|
| 356 |
-
if (category) {
|
| 357 |
-
url.searchParams.set('category', category);
|
| 358 |
-
} else {
|
| 359 |
-
url.searchParams.delete('category');
|
| 360 |
-
}
|
| 361 |
-
url.searchParams.delete('search');
|
| 362 |
-
window.history.replaceState({}, '', url);
|
| 363 |
-
|
| 364 |
-
// Update page title
|
| 365 |
-
updatePageTitle('', category);
|
| 366 |
-
|
| 367 |
-
loadProducts();
|
| 368 |
-
}
|
| 369 |
-
|
| 370 |
-
// Apply Filters
|
| 371 |
-
function applyFilters() {
|
| 372 |
-
currentSort = document.getElementById('sortSelect').value;
|
| 373 |
-
currentPrice = document.getElementById('priceSelect').value;
|
| 374 |
-
currentSeller = document.getElementById('sellerSelect').value;
|
| 375 |
-
page = 0;
|
| 376 |
-
products = [];
|
| 377 |
-
loadProducts();
|
| 378 |
-
}
|
| 379 |
-
|
| 380 |
-
// Load Products
|
| 381 |
-
async function loadProducts() {
|
| 382 |
-
const loadingState = document.getElementById('loadingState');
|
| 383 |
-
const emptyState = document.getElementById('emptyState');
|
| 384 |
-
const productsGrid = document.getElementById('productsGrid');
|
| 385 |
-
|
| 386 |
-
loadingState.classList.remove('hidden');
|
| 387 |
-
emptyState.classList.add('hidden');
|
| 388 |
-
|
| 389 |
-
try {
|
| 390 |
-
let result;
|
| 391 |
-
// Determine max price filter (the edge function search supports a single maxPrice)
|
| 392 |
-
let maxPrice = null;
|
| 393 |
-
if (currentPrice) {
|
| 394 |
-
if (currentPrice === '50000+') {
|
| 395 |
-
maxPrice = 50000;
|
| 396 |
-
} else {
|
| 397 |
-
const [min, max] = currentPrice.split('-');
|
| 398 |
-
maxPrice = parseInt(max);
|
| 399 |
-
}
|
| 400 |
-
}
|
| 401 |
-
|
| 402 |
-
if (currentSearch) {
|
| 403 |
-
// Search within all products (optionally capped by maxPrice)
|
| 404 |
-
result = await window.EdgeFunctions.products.search(currentSearch, maxPrice);
|
| 405 |
-
} else if (currentCategory) {
|
| 406 |
-
// Browse by category with sort + pagination
|
| 407 |
-
result = await window.EdgeFunctions.products.category(currentCategory, currentSort, page + 1);
|
| 408 |
-
} else {
|
| 409 |
-
// No category selected — load all products
|
| 410 |
-
result = await window.EdgeFunctions.products.all(50);
|
| 411 |
-
}
|
| 412 |
-
|
| 413 |
-
if (!result.success) {
|
| 414 |
-
throw new Error(result.error || 'Failed to load products');
|
| 415 |
-
}
|
| 416 |
-
|
| 417 |
-
const data = result.products || [];
|
| 418 |
-
const count = result.count || data.length;
|
| 419 |
-
|
| 420 |
-
document.getElementById('resultsCount').textContent = count;
|
| 421 |
-
|
| 422 |
-
if (data && data.length > 0) {
|
| 423 |
-
products = [...products, ...data];
|
| 424 |
-
renderProducts(data);
|
| 425 |
-
|
| 426 |
-
// Show load more if there are more products
|
| 427 |
-
const loadMoreSection = document.getElementById('loadMoreSection');
|
| 428 |
-
if (products.length < count) {
|
| 429 |
-
loadMoreSection.classList.remove('hidden');
|
| 430 |
-
} else {
|
| 431 |
-
loadMoreSection.classList.add('hidden');
|
| 432 |
-
}
|
| 433 |
-
} else if (products.length === 0) {
|
| 434 |
-
emptyState.classList.remove('hidden');
|
| 435 |
-
productsGrid.innerHTML = '';
|
| 436 |
-
}
|
| 437 |
-
|
| 438 |
-
} catch (error) {
|
| 439 |
-
console.error('Error loading products:', error);
|
| 440 |
-
emptyState.classList.remove('hidden');
|
| 441 |
-
} finally {
|
| 442 |
-
loadingState.classList.add('hidden');
|
| 443 |
-
}
|
| 444 |
-
}
|
| 445 |
-
|
| 446 |
-
// Render Products
|
| 447 |
-
function renderProducts(data) {
|
| 448 |
-
const productsGrid = document.getElementById('productsGrid');
|
| 449 |
-
|
| 450 |
-
if (page === 0) {
|
| 451 |
-
productsGrid.innerHTML = '';
|
| 452 |
-
}
|
| 453 |
-
|
| 454 |
-
data.forEach(product => {
|
| 455 |
-
const card = document.createElement('div');
|
| 456 |
-
card.className = 'product-card bg-white rounded-xl border border-gray-100 overflow-hidden cursor-pointer';
|
| 457 |
-
card.onclick = () => window.location.href = `product.html?id=${product.id}`;
|
| 458 |
-
|
| 459 |
-
const sellerType = product.sellers?.seller_type;
|
| 460 |
-
const sellerBadge = sellerType === 'farmer'
|
| 461 |
-
? '<span class="bg-green-100 text-green-700 text-[10px] px-1.5 py-0.5 rounded-full">Farm Fresh</span>'
|
| 462 |
-
: '';
|
| 463 |
-
|
| 464 |
-
const discount = product.discount_percentage || 0;
|
| 465 |
-
const originalPrice = discount > 0 ? product.price / (1 - discount/100) : null;
|
| 466 |
-
|
| 467 |
-
card.innerHTML = `
|
| 468 |
-
<div class="relative">
|
| 469 |
-
<img src="${product.images?.[0] || 'https://via.placeholder.com/300'}"
|
| 470 |
-
alt="${product.name}"
|
| 471 |
-
class="w-full h-28 lg:h-40 object-cover">
|
| 472 |
-
${discount > 0 ? `<span class="absolute top-2 left-2 bg-red-500 text-white text-[10px] px-1.5 py-0.5 rounded-full">-${discount}%</span>` : ''}
|
| 473 |
-
<button onclick="event.stopPropagation(); toggleWishlist(${product.id})" class="absolute top-2 right-2 w-7 h-7 bg-white rounded-full flex items-center justify-center shadow hover:bg-gray-50">
|
| 474 |
-
<i class="far fa-heart text-gray-400 text-xs"></i>
|
| 475 |
-
</button>
|
| 476 |
-
</div>
|
| 477 |
-
<div class="p-2 lg:p-3">
|
| 478 |
-
<div class="flex items-center gap-1 mb-1">
|
| 479 |
-
${sellerBadge}
|
| 480 |
-
</div>
|
| 481 |
-
<h3 class="text-xs lg:text-sm font-medium text-gray-900 line-clamp-2 mb-1">${product.name}</h3>
|
| 482 |
-
<div class="flex items-center gap-1 mb-2">
|
| 483 |
-
<span class="text-sm lg:text-base font-bold text-gray-900">₦${product.price?.toLocaleString()}</span>
|
| 484 |
-
${originalPrice ? `<span class="text-[10px] text-gray-400 line-through">₦${originalPrice.toFixed(0)}</span>` : ''}
|
| 485 |
-
</div>
|
| 486 |
-
<button onclick="event.stopPropagation(); addToCart(${product.id})" class="w-full bg-orange-500 text-white py-1.5 lg:py-2 rounded-lg text-xs font-medium hover:bg-orange-600 transition-colors">
|
| 487 |
-
Add to Cart
|
| 488 |
-
</button>
|
| 489 |
-
</div>
|
| 490 |
-
`;
|
| 491 |
-
|
| 492 |
-
productsGrid.appendChild(card);
|
| 493 |
-
});
|
| 494 |
-
}
|
| 495 |
-
|
| 496 |
-
// Load More Products
|
| 497 |
-
function loadMoreProducts() {
|
| 498 |
-
page++;
|
| 499 |
-
loadProducts();
|
| 500 |
-
}
|
| 501 |
-
|
| 502 |
-
// Add to Cart
|
| 503 |
-
async function addToCart(productId) {
|
| 504 |
-
try {
|
| 505 |
-
const result = await window.Cart.addToCart(productId, 1);
|
| 506 |
-
|
| 507 |
-
if (!result.success) {
|
| 508 |
-
if (result.requiresAuth) {
|
| 509 |
-
document.getElementById('authModal').classList.remove('hidden');
|
| 510 |
-
document.getElementById('authModal').classList.add('flex');
|
| 511 |
-
return;
|
| 512 |
-
}
|
| 513 |
-
showNotification(result.message || 'Error adding to cart', 'error');
|
| 514 |
-
return;
|
| 515 |
-
}
|
| 516 |
-
|
| 517 |
-
showNotification('Added to cart!');
|
| 518 |
-
|
| 519 |
-
} catch (error) {
|
| 520 |
-
console.error('Add to cart error:', error);
|
| 521 |
-
showNotification('Error adding to cart', 'error');
|
| 522 |
-
}
|
| 523 |
-
}
|
| 524 |
-
|
| 525 |
-
// Toggle Wishlist
|
| 526 |
-
async function toggleWishlist(productId) {
|
| 527 |
-
try {
|
| 528 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 529 |
-
|
| 530 |
-
if (!user) {
|
| 531 |
-
document.getElementById('authModal').classList.remove('hidden');
|
| 532 |
-
document.getElementById('authModal').classList.add('flex');
|
| 533 |
-
return;
|
| 534 |
-
}
|
| 535 |
-
|
| 536 |
-
// Toggle wishlist logic here
|
| 537 |
-
showNotification('Added to wishlist!');
|
| 538 |
-
|
| 539 |
-
} catch (error) {
|
| 540 |
-
console.error('Wishlist error:', error);
|
| 541 |
-
}
|
| 542 |
-
}
|
| 543 |
-
|
| 544 |
-
// Update Cart Count
|
| 545 |
-
async function updateCartCount() {
|
| 546 |
-
try {
|
| 547 |
-
// Cart module handles auth check + DOM updates internally
|
| 548 |
-
await window.Cart.updateCartCountUI();
|
| 549 |
-
} catch (error) {
|
| 550 |
-
console.error('Cart count error:', error);
|
| 551 |
-
}
|
| 552 |
-
}
|
| 553 |
-
|
| 554 |
-
// Close Auth Modal
|
| 555 |
-
function closeAuthModal() {
|
| 556 |
-
document.getElementById('authModal').classList.add('hidden');
|
| 557 |
-
document.getElementById('authModal').classList.remove('flex');
|
| 558 |
-
}
|
| 559 |
-
|
| 560 |
-
// Show Notification
|
| 561 |
-
function showNotification(message, type = 'success') {
|
| 562 |
-
const notification = document.createElement('div');
|
| 563 |
-
notification.className = `fixed top-20 right-4 px-4 py-2 rounded-lg text-sm font-medium z-50 ${type === 'success' ? 'bg-green-500 text-white' : 'bg-red-500 text-white'}`;
|
| 564 |
-
notification.textContent = message;
|
| 565 |
-
document.body.appendChild(notification);
|
| 566 |
-
|
| 567 |
-
setTimeout(() => notification.remove(), 2000);
|
| 568 |
-
}
|
| 569 |
-
|
| 570 |
-
// ===== Search Functions =====
|
| 571 |
-
function handleSearchKeypress(event) {
|
| 572 |
-
if (event.key === 'Enter') {
|
| 573 |
-
performSearch();
|
| 574 |
-
}
|
| 575 |
-
}
|
| 576 |
-
|
| 577 |
-
function performSearch() {
|
| 578 |
-
const searchInput = document.getElementById('searchInput');
|
| 579 |
-
const query = searchInput.value.trim();
|
| 580 |
-
|
| 581 |
-
currentSearch = query;
|
| 582 |
-
currentCategory = '';
|
| 583 |
-
page = 0;
|
| 584 |
-
products = [];
|
| 585 |
-
|
| 586 |
-
// Update URL
|
| 587 |
-
const url = new URL(window.location);
|
| 588 |
-
if (query) {
|
| 589 |
-
url.searchParams.set('search', query);
|
| 590 |
-
} else {
|
| 591 |
-
url.searchParams.delete('search');
|
| 592 |
-
}
|
| 593 |
-
url.searchParams.delete('category');
|
| 594 |
-
window.history.replaceState({}, '', url);
|
| 595 |
-
|
| 596 |
-
// Update title
|
| 597 |
-
updatePageTitle(query, '');
|
| 598 |
-
|
| 599 |
-
// Reset category buttons
|
| 600 |
-
document.querySelectorAll('.filter-btn').forEach(btn => {
|
| 601 |
-
btn.classList.remove('active');
|
| 602 |
-
if (!btn.dataset.category) {
|
| 603 |
-
btn.classList.add('active');
|
| 604 |
-
}
|
| 605 |
-
});
|
| 606 |
-
|
| 607 |
-
loadProducts();
|
| 608 |
-
}
|
| 609 |
-
</script>
|
| 610 |
-
</body>
|
| 611 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checkout.html
DELETED
|
@@ -1,891 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Checkout - Cellex | Nigeria's #1 Marketplace</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
| 10 |
-
<!-- Edge function client (no Supabase SDK) + Cart module -->
|
| 11 |
-
<script src="js/config/edge-functions.js?v=3"></script>
|
| 12 |
-
<script src="js/cart.js?v=11"></script>
|
| 13 |
-
|
| 14 |
-
<script>
|
| 15 |
-
tailwind.config = {
|
| 16 |
-
theme: {
|
| 17 |
-
extend: {
|
| 18 |
-
fontFamily: {
|
| 19 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 20 |
-
},
|
| 21 |
-
colors: {
|
| 22 |
-
brand: {
|
| 23 |
-
50: '#fffbeb',
|
| 24 |
-
100: '#cffafe',
|
| 25 |
-
200: '#a5f3fc',
|
| 26 |
-
300: '#67e8f9',
|
| 27 |
-
400: '#00d4ff',
|
| 28 |
-
500: '#00d4ff',
|
| 29 |
-
600: '#0891b2',
|
| 30 |
-
700: '#b45309',
|
| 31 |
-
},
|
| 32 |
-
dark: {
|
| 33 |
-
900: '#0f172a',
|
| 34 |
-
800: '#1e293b',
|
| 35 |
-
700: '#334155',
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
</script>
|
| 42 |
-
<style>
|
| 43 |
-
* {
|
| 44 |
-
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
@keyframes float {
|
| 48 |
-
0% { transform: translateY(0px); }
|
| 49 |
-
50% { transform: translateY(-10px); }
|
| 50 |
-
100% { transform: translateY(0px); }
|
| 51 |
-
}
|
| 52 |
-
.animate-float {
|
| 53 |
-
animation: float 3s ease-in-out infinite;
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
@keyframes fadeInUp {
|
| 57 |
-
from { opacity: 0; transform: translateY(20px); }
|
| 58 |
-
to { opacity: 1; transform: translateY(0); }
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
@keyframes pulse {
|
| 62 |
-
0% { transform: scale(1); }
|
| 63 |
-
50% { transform: scale(1.05); }
|
| 64 |
-
100% { transform: scale(1); }
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
.fade-in-up {
|
| 68 |
-
animation: fadeInUp 0.4s ease-out forwards;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
.input-field {
|
| 72 |
-
transition: all 0.3s ease;
|
| 73 |
-
}
|
| 74 |
-
.input-field:focus {
|
| 75 |
-
border-color: #00d4ff;
|
| 76 |
-
box-shadow: 0 0 0 15px rgba(245, 158, 11, 0.15);
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
.step-section {
|
| 80 |
-
display: none;
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
.step-section.active {
|
| 84 |
-
display: block;
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
.step-indicator {
|
| 88 |
-
transition: all 0.3s ease;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
.step-indicator.active {
|
| 92 |
-
background: #00d4ff;
|
| 93 |
-
color: white;
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
.step-indicator.completed {
|
| 97 |
-
background: #22c55e;
|
| 98 |
-
color: white;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
.payment-option {
|
| 102 |
-
transition: all 0.3s ease;
|
| 103 |
-
cursor: pointer;
|
| 104 |
-
border: 2px solid #e5e7eb;
|
| 105 |
-
border-radius: 12px;
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
.payment-option:hover {
|
| 109 |
-
border-color: #00d4ff;
|
| 110 |
-
transform: translateY(-2px);
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
.payment-option.selected {
|
| 114 |
-
border-color: #00d4ff;
|
| 115 |
-
background: #fef2f2;
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
.place-order-btn {
|
| 119 |
-
background: linear-gradient(135deg, #00d4ff 0%, #0891b2 100%);
|
| 120 |
-
transition: all 0.3s ease;
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
.place-order-btn:hover {
|
| 124 |
-
transform: translateY(-2px);
|
| 125 |
-
box-shadow: 0 15px 30px rgba(240, 71, 74, 0.15);
|
| 126 |
-
}
|
| 127 |
-
|
| 128 |
-
.place-order-btn:disabled {
|
| 129 |
-
opacity: 0.6;
|
| 130 |
-
cursor: not-allowed;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
/* Custom scrollbar */
|
| 134 |
-
::-webkit-scrollbar {
|
| 135 |
-
width: 6px;
|
| 136 |
-
}
|
| 137 |
-
|
| 138 |
-
::-webkit-scrollbar-track {
|
| 139 |
-
background: #f1f1f1;
|
| 140 |
-
border-radius: 10px;
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
::-webkit-scrollbar-thumb {
|
| 144 |
-
background: #c1c1c1;
|
| 145 |
-
border-radius: 10px;
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
::-webkit-scrollbar-thumb:hover {
|
| 149 |
-
background: #a1a1a1;
|
| 150 |
-
}
|
| 151 |
-
</style>
|
| 152 |
-
</head>
|
| 153 |
-
<body class="bg-gray-50 min-h-screen">
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
<!-- Loading Overlay -->
|
| 157 |
-
<div id="loading-overlay" class="fixed inset-0 bg-black/50 hidden z-50 flex items-center justify-center backdrop-blur-sm">
|
| 158 |
-
<div class="bg-white p-8 rounded-2xl shadow-xl">
|
| 159 |
-
<div class="flex flex-col items-center">
|
| 160 |
-
<div class="animate-spin rounded-full h-12 w-12 border-4 border-cyan-500 border-t-transparent"></div>
|
| 161 |
-
<p class="mt-4 text-gray-700 font-medium" id="loading-text">Processing your order...</p>
|
| 162 |
-
</div>
|
| 163 |
-
</div>
|
| 164 |
-
</div>
|
| 165 |
-
|
| 166 |
-
<!-- Error Toast -->
|
| 167 |
-
<div id="error-toast" class="fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-xl shadow-lg hidden z-50 transition-all transform translate-x-full">
|
| 168 |
-
<div class="flex items-center gap-2">
|
| 169 |
-
<i class="fas fa-exclamation-circle"></i>
|
| 170 |
-
<span id="error-message">Error message</span>
|
| 171 |
-
</div>
|
| 172 |
-
</div>
|
| 173 |
-
|
| 174 |
-
<!-- Success Toast -->
|
| 175 |
-
<div id="success-toast" class="fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-xl shadow-lg hidden z-50 transition-all transform translate-x-full">
|
| 176 |
-
<div class="flex items-center gap-2">
|
| 177 |
-
<i class="fas fa-check-circle"></i>
|
| 178 |
-
<span id="success-message">Success message</span>
|
| 179 |
-
</div>
|
| 180 |
-
</div>
|
| 181 |
-
|
| 182 |
-
<!-- Header -->
|
| 183 |
-
<header class="bg-white shadow-sm sticky top-0 z-40">
|
| 184 |
-
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 185 |
-
<div class="flex justify-between items-center h-16">
|
| 186 |
-
<a href="index.html" class="flex items-center gap-2 group">
|
| 187 |
-
<div class="w-10 h-10 flex items-center justify-center">
|
| 188 |
-
<svg viewBox="0 0 60 60" class="w-full h-full">
|
| 189 |
-
<defs>
|
| 190 |
-
<linearGradient id="headerLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
| 191 |
-
<stop offset="0%" stop-color="#00d4ff"/>
|
| 192 |
-
<stop offset="50%" stop-color="#00d4ff"/>
|
| 193 |
-
<stop offset="100%" stop-color="#0891b2"/>
|
| 194 |
-
</linearGradient>
|
| 195 |
-
<filter id="headerLogoGlow" x="-50%" y="-50%" width="200%" height="200%">
|
| 196 |
-
<feGaussianBlur stdDeviation="1" result="coloredBlur"/>
|
| 197 |
-
<feMerge>
|
| 198 |
-
<feMergeNode in="coloredBlur"/>
|
| 199 |
-
<feMergeNode in="SourceGraphic"/>
|
| 200 |
-
</feMerge>
|
| 201 |
-
</filter>
|
| 202 |
-
</defs>
|
| 203 |
-
<path d="M8 15 L8 50 Q8 54 12 54 L48 54 Q52 54 52 50 L52 15 Q52 11 48 11 L12 11 Q8 11 8 15 Z"
|
| 204 |
-
fill="url(#headerLogoGrad)" filter="url(#headerLogoGlow)"/>
|
| 205 |
-
<path d="M18 11 Q18 2 30 2 Q42 2 42 11"
|
| 206 |
-
fill="none" stroke="url(#headerLogoGrad)" stroke-width="4" stroke-linecap="round"/>
|
| 207 |
-
<path d="M18 24 L38 24 M18 36 L32 36 M18 48 L38 48 M18 24 L18 48"
|
| 208 |
-
fill="none" stroke="#0f172a" stroke-width="3.5" stroke-linecap="round"/>
|
| 209 |
-
<g transform="translate(52, 8)">
|
| 210 |
-
<path d="M0 0 L1.5 4.5 L6 6 L1.5 7.5 L0 12 L-1.5 7.5 L-6 6 L-1.5 4.5 Z" fill="#00d4ff"/>
|
| 211 |
-
</g>
|
| 212 |
-
</svg>
|
| 213 |
-
</div>
|
| 214 |
-
<div class="flex flex-col">
|
| 215 |
-
<span class="text-xl font-extrabold bg-gradient-to-r from-cyan-400 to-cyan-700 bg-clip-text text-transparent leading-tight">Cellex</span>
|
| 216 |
-
<span class="text-[8px] text-gray-400 tracking-widest uppercase -mt-1">Nigeria's #1</span>
|
| 217 |
-
</div>
|
| 218 |
-
</a>
|
| 219 |
-
<div class="flex items-center gap-4">
|
| 220 |
-
<a href="index.html" class="text-gray-600 hover:text-cyan-500 transition-colors">
|
| 221 |
-
<i class="fas fa-home text-xl"></i>
|
| 222 |
-
</a>
|
| 223 |
-
<div class="relative">
|
| 224 |
-
<i class="fas fa-shopping-cart text-xl text-gray-600"></i>
|
| 225 |
-
<span id="cart-count" class="absolute -top-2 -right-2 bg-cyan-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">0</span>
|
| 226 |
-
</div>
|
| 227 |
-
</div>
|
| 228 |
-
</div>
|
| 229 |
-
</nav>
|
| 230 |
-
</header>
|
| 231 |
-
|
| 232 |
-
<!-- Breadcrumb -->
|
| 233 |
-
<div class="bg-white border-b">
|
| 234 |
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
| 235 |
-
<nav class="flex items-center space-x-2 text-sm text-gray-500">
|
| 236 |
-
<a href="index.html" class="hover:text-cyan-500 transition-colors">Home</a>
|
| 237 |
-
<i class="fas fa-chevron-right text-xs"></i>
|
| 238 |
-
<a href="cart.html" class="hover:text-cyan-500 transition-colors">Cart</a>
|
| 239 |
-
<i class="fas fa-chevron-right text-xs"></i>
|
| 240 |
-
<span class="text-gray-900 font-medium">Checkout</span>
|
| 241 |
-
</nav>
|
| 242 |
-
</div>
|
| 243 |
-
</div>
|
| 244 |
-
|
| 245 |
-
<!-- Progress Steps -->
|
| 246 |
-
<div class="bg-gradient-to-r from-primary-500 to-primary-600 py-3 sticky top-16 z-30">
|
| 247 |
-
<div class="max-w-7xl mx-auto px-4">
|
| 248 |
-
<div class="flex items-center justify-between">
|
| 249 |
-
<div class="flex items-center gap-3">
|
| 250 |
-
<div id="step-1-indicator" class="step-indicator active">
|
| 251 |
-
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-white text-cyan-500 font-bold text-sm">1</span>
|
| 252 |
-
<span class="text-white text-sm">Shipping</span>
|
| 253 |
-
</div>
|
| 254 |
-
<div class="hidden sm:block h-1 w-px-4 border-l border-white/30"></div>
|
| 255 |
-
<div id="step-2-indicator" class="step-indicator">
|
| 256 |
-
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-white/70 text-cyan-500 font-bold text-sm">2</span>
|
| 257 |
-
<span class="text-white/70 text-sm">Payment</span>
|
| 258 |
-
</div>
|
| 259 |
-
<div class="hidden sm:block h-1 w-px-4 border-l border-white/30"></div>
|
| 260 |
-
<div id="step-3-indicator" class="step-indicator">
|
| 261 |
-
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-white/70 text-cyan-500 font-bold text-sm">3</span>
|
| 262 |
-
<span class="text-white/70 text-sm">Review</span>
|
| 263 |
-
</div>
|
| 264 |
-
<a href="cart.html" class="text-white text-sm hover:text-white/80 transition-colors">
|
| 265 |
-
<i class="fas fa-arrow-left mr-2"></i>
|
| 266 |
-
Back to Cart
|
| 267 |
-
</a>
|
| 268 |
-
</div>
|
| 269 |
-
</div>
|
| 270 |
-
</div>
|
| 271 |
-
|
| 272 |
-
<!-- Main Content -->
|
| 273 |
-
<main class="max-w pb-20-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
| 274 |
-
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 275 |
-
<!-- Left Column - Steps -->
|
| 276 |
-
<div class="lg:col-span-2 space-y-6">
|
| 277 |
-
<!-- Step 1: Shipping Information -->
|
| 278 |
-
<section id="step-1" class="step-section active bg-white rounded-2xl shadow-lg p-6">
|
| 279 |
-
<div class="flex items-center gap-3 mb-6">
|
| 280 |
-
<div class="w-10 h-10 bg-cyan-500-100 rounded-lg flex items-center justify-center">
|
| 281 |
-
<i class="fas fa-truck text-cyan-500 text-lg"></i>
|
| 282 |
-
</div>
|
| 283 |
-
<h2 class="text-xl font-bold text-gray-900">Shipping Information</h2>
|
| 284 |
-
</div>
|
| 285 |
-
|
| 286 |
-
<form id="shipping-form" class="space-y-4">
|
| 287 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 288 |
-
<div>
|
| 289 |
-
<label for="fullname" class="block text-sm font-medium text-gray-700 mb-1">Full Name *</label>
|
| 290 |
-
<input type="text" id="fullname" required
|
| 291 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-primary/20">
|
| 292 |
-
</div>
|
| 293 |
-
<div>
|
| 294 |
-
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email Address *</label>
|
| 295 |
-
<input type="email" id="email" required
|
| 296 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-primary/20">
|
| 297 |
-
</div>
|
| 298 |
-
</div>
|
| 299 |
-
<div>
|
| 300 |
-
<label for="phone" class="block text-sm font-medium text-gray-700 mb-1">Phone Number *</label>
|
| 301 |
-
<input type="tel" id="phone" required
|
| 302 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-primary/20">
|
| 303 |
-
</div>
|
| 304 |
-
<div>
|
| 305 |
-
<label for="address" class="block text-sm font-medium text-gray-700 mb-1">Street Address *</label>
|
| 306 |
-
<input type="text" id="address" required
|
| 307 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-primary/20">
|
| 308 |
-
</div>
|
| 309 |
-
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
| 310 |
-
<div>
|
| 311 |
-
<label for="city" class="block text-sm font-medium text-gray-700 mb-1">City *</label>
|
| 312 |
-
<input type="text" id="city" required
|
| 313 |
-
class="input-field px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-primary/20">
|
| 314 |
-
</div>
|
| 315 |
-
<div>
|
| 316 |
-
<label for="state" class="block text-sm font-medium text-gray-700 mb-1">State *</label>
|
| 317 |
-
<input type="text" id="state" required
|
| 318 |
-
class="input-field px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-primary/20">
|
| 319 |
-
</div>
|
| 320 |
-
<div>
|
| 321 |
-
<label for="zipcode" class="block text-sm font-medium text-gray-700 mb-1">ZIP Code *</label>
|
| 322 |
-
<input type="text" id="zipcode" required
|
| 323 |
-
class="input-field px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-primary/20">
|
| 324 |
-
</div>
|
| 325 |
-
</div>
|
| 326 |
-
</form>
|
| 327 |
-
</section>
|
| 328 |
-
|
| 329 |
-
<!-- Step 2: Payment Method -->
|
| 330 |
-
<section id="step-2" class="step-section bg-white rounded-2xl shadow-lg p-6">
|
| 331 |
-
<div class="flex items-center gap-3 mb-6">
|
| 332 |
-
<div class="w-10 h-10 bg-cyan-500-100 rounded-lg flex items-center justify-center">
|
| 333 |
-
<i class="fas fa-credit-card text-cyan-500 text-lg"></i>
|
| 334 |
-
</div>
|
| 335 |
-
<h2 class="text-xl font-bold text-gray-900">Payment Method</h2>
|
| 336 |
-
</div>
|
| 337 |
-
|
| 338 |
-
<!-- EeshaPay Option -->
|
| 339 |
-
<div class="mb-4">
|
| 340 |
-
<label class="payment-option block p-4 border-2 border-gray-200 rounded-xl cursor-pointer" onclick="selectPayment('eeshapay')">
|
| 341 |
-
<div class="flex items-center justify-between">
|
| 342 |
-
<div class="flex items-center gap-3">
|
| 343 |
-
<div class="w-12 h-12 bg-gradient-to-br from-gray-800 to-gray-900 rounded-lg flex items-center justify-center">
|
| 344 |
-
<span class="text-white font-bold text-xs">EeshaPay</span>
|
| 345 |
-
</div>
|
| 346 |
-
<div>
|
| 347 |
-
<p class="font-semibold text-gray-900">EeshaPay Wallet</p>
|
| 348 |
-
<p class="text-sm text-gray-500">Pay with your wallet balance</p>
|
| 349 |
-
<p class="text-xs text-green-600 mt-1" id="wallet-balance">Balance: Loading...</p>
|
| 350 |
-
</div>
|
| 351 |
-
</div>
|
| 352 |
-
<div class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center" id="eeshapay-check">
|
| 353 |
-
<i class="fas fa-check text-cyan-500 hidden"></i>
|
| 354 |
-
</div>
|
| 355 |
-
</div>
|
| 356 |
-
</label>
|
| 357 |
-
</div>
|
| 358 |
-
|
| 359 |
-
<!-- Credit Card Option -->
|
| 360 |
-
<div class="mb-4">
|
| 361 |
-
<label class="payment-option block p-4 border-2 border-gray-200 rounded-xl cursor-pointer" onclick="selectPayment('card')">
|
| 362 |
-
<div class="flex items-center justify-between">
|
| 363 |
-
<div class="flex items-center gap-3">
|
| 364 |
-
<div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-700 rounded-lg flex items-center justify-center">
|
| 365 |
-
<i class="fas fa-credit-card text-white text-lg"></i>
|
| 366 |
-
</div>
|
| 367 |
-
<div>
|
| 368 |
-
<p class="font-semibold text-gray-900">Credit / Debit Card</p>
|
| 369 |
-
<p class="text-sm text-gray-500">Visa, Mastercard, Amex</p>
|
| 370 |
-
</div>
|
| 371 |
-
</div>
|
| 372 |
-
<div class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center" id="card-check">
|
| 373 |
-
<i class="fas fa-check text-cyan-500 hidden"></i>
|
| 374 |
-
</div>
|
| 375 |
-
</div>
|
| 376 |
-
</label>
|
| 377 |
-
</div>
|
| 378 |
-
|
| 379 |
-
<!-- PalmPay Bank Transfer Option -->
|
| 380 |
-
<div class="mb-4">
|
| 381 |
-
<label class="payment-option block p-4 border-2 border-gray-200 rounded-xl cursor-pointer" onclick="selectPayment('palmpay')">
|
| 382 |
-
<div class="flex items-center justify-between">
|
| 383 |
-
<div class="flex items-center gap-3">
|
| 384 |
-
<div class="w-12 h-12 bg-gradient-to-br from-cyan-400 to-cyan-700 rounded-lg flex items-center justify-center">
|
| 385 |
-
<i class="fas fa-university text-white text-lg"></i>
|
| 386 |
-
</div>
|
| 387 |
-
<div>
|
| 388 |
-
<p class="font-semibold text-gray-900">Bank Transfer (PalmPay)</p>
|
| 389 |
-
<p class="text-sm text-gray-500">Transfer to PalmPay · Auto-verified via AI</p>
|
| 390 |
-
<p class="text-xs text-cyan-600 mt-1">✓ Instant verification — no waiting!</p>
|
| 391 |
-
</div>
|
| 392 |
-
</div>
|
| 393 |
-
<div class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center" id="palmpay-check">
|
| 394 |
-
<i class="fas fa-check text-cyan-500 hidden"></i>
|
| 395 |
-
</div>
|
| 396 |
-
</div>
|
| 397 |
-
</label>
|
| 398 |
-
</div>
|
| 399 |
-
|
| 400 |
-
<!-- Cash on Delivery Option -->
|
| 401 |
-
<div class="mb-4">
|
| 402 |
-
<label class="payment-option block p-4 border-2 border-gray-200 rounded-xl cursor-pointer" onclick="selectPayment('cod')">
|
| 403 |
-
<div class="flex items-center justify-between">
|
| 404 |
-
<div class="flex items-center gap-3">
|
| 405 |
-
<div class="w-12 h-12 bg-gradient-to-br from-green-500 to-green-700 rounded-lg flex items-center justify-center">
|
| 406 |
-
<i class="fas fa-money-bill-wave text-white text-lg"></i>
|
| 407 |
-
</div>
|
| 408 |
-
<div>
|
| 409 |
-
<p class="font-semibold text-gray-900">Cash on Delivery</p>
|
| 410 |
-
<p class="text-sm text-gray-500">Pay when you receive your order</p>
|
| 411 |
-
<p class="text-xs text-cyan-600 mt-1">+₦5.00 COD fee</p>
|
| 412 |
-
</div>
|
| 413 |
-
</div>
|
| 414 |
-
<div class="w-6 h-6 border-2 border-gray-300 rounded-full flex items-center justify-center" id="cod-check">
|
| 415 |
-
<i class="fas fa-check text-cyan-500 hidden"></i>
|
| 416 |
-
</div>
|
| 417 |
-
</div>
|
| 418 |
-
</label>
|
| 419 |
-
</div>
|
| 420 |
-
|
| 421 |
-
<!-- Card Details Form -->
|
| 422 |
-
<div id="card-form" class="hidden mt-4 p-4 bg-gray-50 rounded-xl">
|
| 423 |
-
<h3 class="font-semibold text-gray-900 mb-3">Card Details</h3>
|
| 424 |
-
<div class="space-y-4">
|
| 425 |
-
<div>
|
| 426 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Card Number</label>
|
| 427 |
-
<input type="text" id="card-number" placeholder="1234 5678 9012 3456" maxlength="19"
|
| 428 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 429 |
-
</div>
|
| 430 |
-
<div class="grid grid-cols-2 gap-4">
|
| 431 |
-
<div>
|
| 432 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Expiry Date</label>
|
| 433 |
-
<input type="text" id="card-expiry" placeholder="MM/YY" maxlength="7"
|
| 434 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 435 |
-
</div>
|
| 436 |
-
<div>
|
| 437 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">CVV</label>
|
| 438 |
-
<input type="text" id="card-cvv" placeholder="123" maxlength="4"
|
| 439 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 440 |
-
</div>
|
| 441 |
-
</div>
|
| 442 |
-
<div>
|
| 443 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Name on Card</label>
|
| 444 |
-
<input type="text" id="card-name" placeholder="John Doe"
|
| 445 |
-
class="input-field w-full px-4 py-3 border border-gray-200 rounded-xl">
|
| 446 |
-
</div>
|
| 447 |
-
</div>
|
| 448 |
-
</div>
|
| 449 |
-
</section>
|
| 450 |
-
|
| 451 |
-
<!-- Step 3: Review & Place Order -->
|
| 452 |
-
<section id="step-3" class="step-section bg-white rounded-2xl shadow-lg p-6">
|
| 453 |
-
<div class="flex items-center gap-3 mb-6">
|
| 454 |
-
<div class="w-10 h-10 bg-cyan-500-100 rounded-lg flex items-center justify-center">
|
| 455 |
-
<i class="fas fa-check-circle text-cyan-500 text-lg"></i>
|
| 456 |
-
</div>
|
| 457 |
-
<h2 class="text-xl font-bold text-gray-900">Review & Place Order</h2>
|
| 458 |
-
</div>
|
| 459 |
-
|
| 460 |
-
<!-- Order Summary Review -->
|
| 461 |
-
<div id="review-summary" class="space-y-4">
|
| 462 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 463 |
-
<h4 class="text-sm font-semibold text-gray-700 mb-2">Shipping Address</h4>
|
| 464 |
-
<p class="text-gray-600 text-sm" id="review-shipping"></p>
|
| 465 |
-
</div>
|
| 466 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 467 |
-
<h4 class="text-sm font-semibold text-gray-700 mb-2">Payment Method</h4>
|
| 468 |
-
<p class="text-gray-600 text-sm" id="review-payment"></p>
|
| 469 |
-
</div>
|
| 470 |
-
<div class="bg-gray-50 rounded-xl p-4">
|
| 471 |
-
<h4 class="text-sm font-semibold text-gray-700 mb-2">Order Items</h4>
|
| 472 |
-
<div id="review-items" class="text-gray-600 text-sm"></div>
|
| 473 |
-
</div>
|
| 474 |
-
</div>
|
| 475 |
-
|
| 476 |
-
<!-- Place Order Button -->
|
| 477 |
-
<button onclick="placeOrder()" id="place-order-btn"
|
| 478 |
-
class="place-order-btn w-full py-4 text-white font-semibold rounded-xl flex items-center justify-center gap-2">
|
| 479 |
-
<i class="fas fa-lock"></i>
|
| 480 |
-
<span id="place-order-text">Place Order</span>
|
| 481 |
-
</button>
|
| 482 |
-
|
| 483 |
-
<p class="text-center text-gray-500 text-sm mt-4">
|
| 484 |
-
<i class="fas fa-shield-alt mr-1"></i>
|
| 485 |
-
Secure checkout with SSL encryption
|
| 486 |
-
</p>
|
| 487 |
-
</section>
|
| 488 |
-
</div>
|
| 489 |
-
|
| 490 |
-
<!-- Right Column - Order Summary -->
|
| 491 |
-
<div class="lg:col-span-1">
|
| 492 |
-
<div class="bg-white rounded-2xl shadow-lg p-6 sticky top-24">
|
| 493 |
-
<h3 class="text-lg font-bold text-gray-900 mb-4">Order Summary</h3>
|
| 494 |
-
<div id="order-items" class="space-y-4 max-h-64 overflow-y-auto mb-4">
|
| 495 |
-
<!-- Items populated by JS -->
|
| 496 |
-
</div>
|
| 497 |
-
|
| 498 |
-
<div class="border-t pt-4 mt-4 space-y-3">
|
| 499 |
-
<div class="flex justify-between text-gray-600">
|
| 500 |
-
<span>Subtotal</span>
|
| 501 |
-
<span id="subtotal" class="font-semibold text-gray-900">₦0.00</span>
|
| 502 |
-
</div>
|
| 503 |
-
<div class="flex justify-between text-gray-600">
|
| 504 |
-
<span>Shipping</span>
|
| 505 |
-
<span id="shipping-cost" class="font-semibold text-gray-900">₦0.00</span>
|
| 506 |
-
</div>
|
| 507 |
-
<div class="flex justify-between text-gray-600">
|
| 508 |
-
<span>Tax (10%)</span>
|
| 509 |
-
<span id="tax" class="font-semibold text-gray-900">₦0.00</span>
|
| 510 |
-
</div>
|
| 511 |
-
<div class="flex justify-between text-gray-600 pt-4 border-t border-dashed">
|
| 512 |
-
<span>COD Fee</span>
|
| 513 |
-
<span id="cod-fee" class="font-semibold text-cyan-600">₦0.00</span>
|
| 514 |
-
</div>
|
| 515 |
-
<div class="flex justify-between items-center pt-4 border-t-2 border-gray-900">
|
| 516 |
-
<span class="text-lg font-bold text-gray-900">Total</span>
|
| 517 |
-
<span id="total" class="text-2xl font-bold text-cyan-500">₦0.00</span>
|
| 518 |
-
</div>
|
| 519 |
-
</div>
|
| 520 |
-
</div>
|
| 521 |
-
</div>
|
| 522 |
-
</div>
|
| 523 |
-
|
| 524 |
-
<!-- Fixed Navigation -->
|
| 525 |
-
<div class="fixed bottom-6 left-6 md:left-auto md:right-6 lg:right-12 z-50 bg-white/95 backdrop-blur-sm shadow-lg rounded-full px-6 py-4 max-w-3xl mx-auto flex items-center justify-between gap-4">
|
| 526 |
-
<button id="back-btn" onclick="goBack()" class="px-6 py-3 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition-colors">
|
| 527 |
-
<i class="fas fa-arrow-left mr-2"></i>
|
| 528 |
-
Back
|
| 529 |
-
</button>
|
| 530 |
-
<div class="flex items-center gap-4">
|
| 531 |
-
<span class="text-sm text-gray-500">Step <span id="step-label">1</span> of 3</span>
|
| 532 |
-
</div>
|
| 533 |
-
<button id="continue-btn" onclick="goNext()" class="px-6 py-3 bg-cyan-500 text-white rounded-lg hover:bg-cyan-500-600 transition-colors">
|
| 534 |
-
<span id="continue-text">Continue</span>
|
| 535 |
-
<i class="fas fa-arrow-right ml-2"></i>
|
| 536 |
-
</button>
|
| 537 |
-
</div>
|
| 538 |
-
</div>
|
| 539 |
-
|
| 540 |
-
<script>
|
| 541 |
-
// State
|
| 542 |
-
let currentStep = 1;
|
| 543 |
-
let pendingOrder = null;
|
| 544 |
-
let selectedPayment = 'eeshapay';
|
| 545 |
-
let currentUser = null;
|
| 546 |
-
let walletBalance = 0;
|
| 547 |
-
|
| 548 |
-
// Initialize
|
| 549 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 550 |
-
await checkAuth();
|
| 551 |
-
if (currentUser) {
|
| 552 |
-
await loadCheckoutData();
|
| 553 |
-
}
|
| 554 |
-
});
|
| 555 |
-
|
| 556 |
-
// Check authentication
|
| 557 |
-
async function checkAuth() {
|
| 558 |
-
const { success, user } = await window.EdgeFunctions.auth.checkSession();
|
| 559 |
-
currentUser = user || null;
|
| 560 |
-
|
| 561 |
-
if (!currentUser) {
|
| 562 |
-
showToast('Please login to continue checkout', 'error');
|
| 563 |
-
setTimeout(() => {
|
| 564 |
-
window.location.href = 'login.html?redirect=checkout';
|
| 565 |
-
}, 1500);
|
| 566 |
-
}
|
| 567 |
-
}
|
| 568 |
-
|
| 569 |
-
// Load checkout data (cart items, wallet balance, total) via edge function.
|
| 570 |
-
// The edge function returns everything prepare() needs atomically —
|
| 571 |
-
// no Supabase calls, no localStorage, no pending_orders table.
|
| 572 |
-
async function loadCheckoutData() {
|
| 573 |
-
try {
|
| 574 |
-
const result = await window.EdgeFunctions.checkout.prepare();
|
| 575 |
-
|
| 576 |
-
if (!result.success) {
|
| 577 |
-
showToast(result.error || 'Failed to load checkout. Please try again.', 'error');
|
| 578 |
-
return;
|
| 579 |
-
}
|
| 580 |
-
|
| 581 |
-
const cartItems = result.cartItems || [];
|
| 582 |
-
walletBalance = result.walletBalance || 0;
|
| 583 |
-
|
| 584 |
-
// Empty cart → redirect to cart page
|
| 585 |
-
if (!cartItems.length) {
|
| 586 |
-
showToast('Your cart is empty. Redirecting to cart...', 'error');
|
| 587 |
-
setTimeout(() => window.location.href = 'cart.html', 1500);
|
| 588 |
-
return;
|
| 589 |
-
}
|
| 590 |
-
|
| 591 |
-
// Normalize cart items into the shape renderOrderSummary() expects.
|
| 592 |
-
const items = cartItems.map(item => ({
|
| 593 |
-
image: item.image || item.products?.image_url || item.products?.image || '',
|
| 594 |
-
name: item.name || item.products?.name || 'Product',
|
| 595 |
-
quantity: item.quantity || 1,
|
| 596 |
-
price: (item.price != null ? item.price : (item.products?.price || 0)),
|
| 597 |
-
}));
|
| 598 |
-
|
| 599 |
-
const subtotal = items.reduce((s, it) => s + it.price * it.quantity, 0);
|
| 600 |
-
const total = (result.total != null ? result.total : subtotal);
|
| 601 |
-
|
| 602 |
-
pendingOrder = {
|
| 603 |
-
items,
|
| 604 |
-
subtotal,
|
| 605 |
-
shipping: 0,
|
| 606 |
-
tax: 0,
|
| 607 |
-
total,
|
| 608 |
-
};
|
| 609 |
-
|
| 610 |
-
// Update wallet balance display
|
| 611 |
-
document.getElementById('wallet-balance').textContent = `Balance: ₦${walletBalance.toFixed(2)}`;
|
| 612 |
-
|
| 613 |
-
renderOrderSummary();
|
| 614 |
-
} catch (error) {
|
| 615 |
-
console.error('Error loading checkout data:', error);
|
| 616 |
-
showToast('Error loading checkout. Please try again.', 'error');
|
| 617 |
-
}
|
| 618 |
-
}
|
| 619 |
-
|
| 620 |
-
// Render order summary in sidebar
|
| 621 |
-
function renderOrderSummary() {
|
| 622 |
-
if (!pendingOrder) return;
|
| 623 |
-
|
| 624 |
-
const container = document.getElementById('order-items');
|
| 625 |
-
container.innerHTML = pendingOrder.items.map(item => `
|
| 626 |
-
<div class="flex items-center gap-3 py-2">
|
| 627 |
-
<div class="w-12 h-12 bg-gray-100 rounded-lg overflow-hidden">
|
| 628 |
-
<img src="${item.image || 'https://via.placeholder.com/48'}" alt="${item.name}" class="w-full h-full object-cover">
|
| 629 |
-
</div>
|
| 630 |
-
<div class="flex-1 min-w-0">
|
| 631 |
-
<p class="text-sm font-medium text-gray-900 truncate">${item.name}</p>
|
| 632 |
-
<p class="text-xs text-gray-500">Qty: ${item.quantity}</p>
|
| 633 |
-
</div>
|
| 634 |
-
<div class="text-right">
|
| 635 |
-
<p class="text-sm font-semibold text-gray-900">₦${(item.price * item.quantity).toFixed(2)}</p>
|
| 636 |
-
</div>
|
| 637 |
-
</div>
|
| 638 |
-
`).join('');
|
| 639 |
-
|
| 640 |
-
// Update totals
|
| 641 |
-
document.getElementById('subtotal').textContent = `₦${(pendingOrder.subtotal || 0).toFixed(2)}`;
|
| 642 |
-
document.getElementById('shipping-cost').textContent = `₦${(pendingOrder.shipping || 0).toFixed(2)}`;
|
| 643 |
-
document.getElementById('tax').textContent = `₦${(pendingOrder.tax || 0).toFixed(2)}`;
|
| 644 |
-
|
| 645 |
-
const codFee = selectedPayment === 'cod' ? 5 : 0;
|
| 646 |
-
document.getElementById('cod-fee').textContent = `₦${codFee.toFixed(2)}`;
|
| 647 |
-
document.getElementById('cod-fee').parentElement.classList.toggle('hidden', selectedPayment !== 'cod');
|
| 648 |
-
|
| 649 |
-
const total = (pendingOrder.total || 0) + codFee;
|
| 650 |
-
document.getElementById('total').textContent = `₦${total.toFixed(2)}`;
|
| 651 |
-
}
|
| 652 |
-
|
| 653 |
-
// Step navigation
|
| 654 |
-
function showStep(step) {
|
| 655 |
-
currentStep = step;
|
| 656 |
-
|
| 657 |
-
// Hide all steps
|
| 658 |
-
document.querySelectorAll('.step-section').forEach(s => s.classList.remove('active'));
|
| 659 |
-
|
| 660 |
-
// Show current step
|
| 661 |
-
document.getElementById(`step-${step}`).classList.add('active');
|
| 662 |
-
|
| 663 |
-
// Update indicators
|
| 664 |
-
document.querySelectorAll('.step-indicator').forEach((indicator, index) => {
|
| 665 |
-
indicator.classList.remove('active', 'completed');
|
| 666 |
-
if (index + 1 < step) {
|
| 667 |
-
indicator.classList.add('completed');
|
| 668 |
-
} else if (index + 1 === step) {
|
| 669 |
-
indicator.classList.add('active');
|
| 670 |
-
}
|
| 671 |
-
});
|
| 672 |
-
|
| 673 |
-
// Update buttons
|
| 674 |
-
const backBtn = document.getElementById('back-btn');
|
| 675 |
-
const continueBtn = document.getElementById('continue-btn');
|
| 676 |
-
const continueText = document.getElementById('continue-text');
|
| 677 |
-
|
| 678 |
-
backBtn.classList.toggle('hidden', step === 1);
|
| 679 |
-
|
| 680 |
-
if (step === 3) {
|
| 681 |
-
continueBtn.classList.add('hidden');
|
| 682 |
-
document.getElementById('place-order-btn').classList.remove('hidden');
|
| 683 |
-
} else {
|
| 684 |
-
continueBtn.classList.remove('hidden');
|
| 685 |
-
document.getElementById('place-order-btn').classList.add('hidden');
|
| 686 |
-
continueText.textContent = step === 1 ? 'Continue to Payment' : 'Review Order';
|
| 687 |
-
}
|
| 688 |
-
|
| 689 |
-
// Update step label
|
| 690 |
-
document.getElementById('step-label').textContent = step;
|
| 691 |
-
}
|
| 692 |
-
|
| 693 |
-
function goNext() {
|
| 694 |
-
if (currentStep === 1) {
|
| 695 |
-
// Validate shipping form
|
| 696 |
-
if (!validateShippingForm()) return;
|
| 697 |
-
showStep(2);
|
| 698 |
-
} else if (currentStep === 2) {
|
| 699 |
-
showStep(3);
|
| 700 |
-
updateReview();
|
| 701 |
-
}
|
| 702 |
-
}
|
| 703 |
-
|
| 704 |
-
function goBack() {
|
| 705 |
-
if (currentStep > 1) {
|
| 706 |
-
showStep(currentStep - 1);
|
| 707 |
-
}
|
| 708 |
-
}
|
| 709 |
-
|
| 710 |
-
// Validate shipping form
|
| 711 |
-
function validateShippingForm() {
|
| 712 |
-
const fields = ['fullname', 'email', 'phone', 'address', 'city', 'state', 'zipcode'];
|
| 713 |
-
for (const field of fields) {
|
| 714 |
-
const input = document.getElementById(field);
|
| 715 |
-
if (!input.value.trim()) {
|
| 716 |
-
showToast(`Please fill in ${field.replace('_', ' ')}`, 'error');
|
| 717 |
-
input.focus();
|
| 718 |
-
return false;
|
| 719 |
-
}
|
| 720 |
-
}
|
| 721 |
-
|
| 722 |
-
// Validate email format
|
| 723 |
-
const email = document.getElementById('email').value;
|
| 724 |
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
| 725 |
-
showToast('Please enter a valid email address', 'error');
|
| 726 |
-
return false;
|
| 727 |
-
}
|
| 728 |
-
|
| 729 |
-
return true;
|
| 730 |
-
}
|
| 731 |
-
|
| 732 |
-
// Select payment method
|
| 733 |
-
function selectPayment(method) {
|
| 734 |
-
selectedPayment = method;
|
| 735 |
-
|
| 736 |
-
// Update UI
|
| 737 |
-
document.querySelectorAll('.payment-option').forEach(opt => {
|
| 738 |
-
opt.classList.remove('selected');
|
| 739 |
-
opt.querySelector('.fa-check').classList.add('hidden');
|
| 740 |
-
});
|
| 741 |
-
|
| 742 |
-
const selected = document.querySelector(`[onclick="selectPayment('${method}')"]`);
|
| 743 |
-
selected.classList.add('selected');
|
| 744 |
-
selected.querySelector('.fa-check').classList.remove('hidden');
|
| 745 |
-
|
| 746 |
-
// Show/hide card form
|
| 747 |
-
document.getElementById('card-form').classList.toggle('hidden', method !== 'card');
|
| 748 |
-
|
| 749 |
-
// Update totals if COD
|
| 750 |
-
renderOrderSummary();
|
| 751 |
-
}
|
| 752 |
-
|
| 753 |
-
// Update review section
|
| 754 |
-
function updateReview() {
|
| 755 |
-
// Shipping address
|
| 756 |
-
const shipping = `
|
| 757 |
-
<p>${document.getElementById('fullname').value}</p>
|
| 758 |
-
<p>${document.getElementById('address').value}</p>
|
| 759 |
-
<p>${document.getElementById('city').value}, ${document.getElementById('state').value} ${document.getElementById('zipcode').value}</p>
|
| 760 |
-
`;
|
| 761 |
-
document.getElementById('review-shipping').innerHTML = shipping;
|
| 762 |
-
|
| 763 |
-
// Payment method
|
| 764 |
-
let paymentText = '';
|
| 765 |
-
if (selectedPayment === 'eeshapay') {
|
| 766 |
-
paymentText = `<p class="font-semibold text-cyan-500">EeshaPay Wallet</p><p class="text-sm text-gray-500">Balance: ₦${walletBalance.toFixed(2)}</p>`;
|
| 767 |
-
} else if (selectedPayment === 'card') {
|
| 768 |
-
const last4 = document.getElementById('card-number').value.slice(-4);
|
| 769 |
-
paymentText = `<p class="font-semibold">Card ending in ${last4}</p><p class="text-sm text-gray-500">${document.getElementById('card-name').value}</p>`;
|
| 770 |
-
} else {
|
| 771 |
-
paymentText = `<p class="font-semibold text-green-600">Cash on Delivery</p><p class="text-sm text-gray-500">Pay when you receive your order (+₦5.00 fee)</p>`;
|
| 772 |
-
}
|
| 773 |
-
document.getElementById('review-payment').innerHTML = paymentText;
|
| 774 |
-
|
| 775 |
-
// Order items
|
| 776 |
-
const itemsHtml = pendingOrder.items.map(item => `
|
| 777 |
-
<div class="flex justify-between py-1">
|
| 778 |
-
<span>${item.name} x ${item.quantity}</span>
|
| 779 |
-
<span>₦${(item.price * item.quantity).toFixed(2)}</span>
|
| 780 |
-
</div>
|
| 781 |
-
`).join('');
|
| 782 |
-
document.getElementById('review-items').innerHTML = itemsHtml;
|
| 783 |
-
}
|
| 784 |
-
|
| 785 |
-
// Place order
|
| 786 |
-
async function placeOrder() {
|
| 787 |
-
// Skip validation for PalmPay (payment happens on the next page)
|
| 788 |
-
if (selectedPayment === 'palmpay') {
|
| 789 |
-
// Just proceed to place the order
|
| 790 |
-
} else if (selectedPayment === 'card') {
|
| 791 |
-
const cardNumber = document.getElementById('card-number').value;
|
| 792 |
-
const cardExpiry = document.getElementById('card-expiry').value;
|
| 793 |
-
const cardCvv = document.getElementById('card-cvv').value;
|
| 794 |
-
const cardName = document.getElementById('card-name').value;
|
| 795 |
-
|
| 796 |
-
if (!cardNumber || cardNumber.length < 16) {
|
| 797 |
-
showToast('Please enter a valid card number', 'error');
|
| 798 |
-
return;
|
| 799 |
-
}
|
| 800 |
-
if (!cardExpiry || !cardCvv || !cardName) {
|
| 801 |
-
showToast('Please fill in all card details', 'error');
|
| 802 |
-
return;
|
| 803 |
-
}
|
| 804 |
-
}
|
| 805 |
-
|
| 806 |
-
// Check EeshaPay balance
|
| 807 |
-
if (selectedPayment === 'eeshapay') {
|
| 808 |
-
if (walletBalance < pendingOrder.total) {
|
| 809 |
-
showToast('Insufficient wallet balance. Please add funds or choose another payment method.', 'error');
|
| 810 |
-
return;
|
| 811 |
-
}
|
| 812 |
-
}
|
| 813 |
-
|
| 814 |
-
// Show loading
|
| 815 |
-
showLoading(true);
|
| 816 |
-
|
| 817 |
-
try {
|
| 818 |
-
const shippingAddress = {
|
| 819 |
-
fullname: document.getElementById('fullname').value,
|
| 820 |
-
email: document.getElementById('email').value,
|
| 821 |
-
phone: document.getElementById('phone').value,
|
| 822 |
-
address: document.getElementById('address').value,
|
| 823 |
-
city: document.getElementById('city').value,
|
| 824 |
-
state: document.getElementById('state').value,
|
| 825 |
-
zipcode: document.getElementById('zipcode').value
|
| 826 |
-
};
|
| 827 |
-
|
| 828 |
-
// The edge function handles ALL atomic operations server-side:
|
| 829 |
-
// debit wallet, create order, create transaction, clear cart.
|
| 830 |
-
// The frontend just calls placeOrder() and reads the result.
|
| 831 |
-
const result = await window.EdgeFunctions.checkout.placeOrder(shippingAddress);
|
| 832 |
-
|
| 833 |
-
if (!result.success) {
|
| 834 |
-
throw new Error(result.error || 'Failed to place order');
|
| 835 |
-
}
|
| 836 |
-
|
| 837 |
-
// Update local wallet balance from server response
|
| 838 |
-
if (typeof result.newBalance === 'number') {
|
| 839 |
-
walletBalance = result.newBalance;
|
| 840 |
-
}
|
| 841 |
-
|
| 842 |
-
// Show success
|
| 843 |
-
showLoading(false);
|
| 844 |
-
showToast('Order placed successfully!', 'success');
|
| 845 |
-
|
| 846 |
-
// Redirect to payment page (for PalmPay) or confirmation
|
| 847 |
-
setTimeout(() => {
|
| 848 |
-
if (selectedPayment === 'palmpay') {
|
| 849 |
-
// Build the payment URL with order details
|
| 850 |
-
const total = pendingOrder?.total || 0;
|
| 851 |
-
const items = pendingOrder?.items?.map(i => `${i.quantity}x ${i.product?.name || i.product_name || 'Item'}`).join(', ') || 'Order items';
|
| 852 |
-
const count = pendingOrder?.items?.length || 1;
|
| 853 |
-
window.location.href = `payment.html?orderId=${result.orderId}&total=${total}&items=${encodeURIComponent(items)}&count=${count}`;
|
| 854 |
-
} else {
|
| 855 |
-
window.location.href = `order-confirmation.html?orderId=${result.orderId}`;
|
| 856 |
-
}
|
| 857 |
-
}, 1000);
|
| 858 |
-
|
| 859 |
-
} catch (error) {
|
| 860 |
-
console.error('Error placing order:', error);
|
| 861 |
-
showLoading(false);
|
| 862 |
-
showToast(error.message || 'Error placing order. Please try again.', 'error');
|
| 863 |
-
}
|
| 864 |
-
}
|
| 865 |
-
|
| 866 |
-
// UI Helpers
|
| 867 |
-
function showLoading(show) {
|
| 868 |
-
document.getElementById('loading-overlay').classList.toggle('hidden', !show);
|
| 869 |
-
}
|
| 870 |
-
|
| 871 |
-
function showToast(message, type) {
|
| 872 |
-
const toast = document.getElementById(`${type}-toast`);
|
| 873 |
-
document.getElementById(`${type}-message`).textContent = message;
|
| 874 |
-
toast.classList.remove('hidden');
|
| 875 |
-
toast.classList.remove('translate-x-full');
|
| 876 |
-
|
| 877 |
-
setTimeout(() => {
|
| 878 |
-
toast.classList.add('translate-x-full');
|
| 879 |
-
setTimeout(() => toast.classList.add('hidden'), 300);
|
| 880 |
-
}, 3000);
|
| 881 |
-
}
|
| 882 |
-
|
| 883 |
-
// Format card number input
|
| 884 |
-
document.getElementById('card-number')?.addEventListener('input', function(e) {
|
| 885 |
-
let value = e.target.value.replace(/\s/g, '').replace(/\D/g, '');
|
| 886 |
-
value = value.replace(/(\d{4})/g, '₦1 ').trim();
|
| 887 |
-
e.target.value = value;
|
| 888 |
-
});
|
| 889 |
-
</script>
|
| 890 |
-
</body>
|
| 891 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
| 3 |
+
"style": "new-york",
|
| 4 |
+
"rsc": true,
|
| 5 |
+
"tsx": true,
|
| 6 |
+
"tailwind": {
|
| 7 |
+
"config": "",
|
| 8 |
+
"css": "src/app/globals.css",
|
| 9 |
+
"baseColor": "neutral",
|
| 10 |
+
"cssVariables": true,
|
| 11 |
+
"prefix": ""
|
| 12 |
+
},
|
| 13 |
+
"aliases": {
|
| 14 |
+
"components": "@/components",
|
| 15 |
+
"utils": "@/lib/utils",
|
| 16 |
+
"ui": "@/components/ui",
|
| 17 |
+
"lib": "@/lib",
|
| 18 |
+
"hooks": "@/hooks"
|
| 19 |
+
},
|
| 20 |
+
"iconLibrary": "lucide"
|
| 21 |
+
}
|
css/common.css
DELETED
|
@@ -1,360 +0,0 @@
|
|
| 1 |
-
/* Base styles and variables — cyan + white futuristic theme */
|
| 2 |
-
:root {
|
| 3 |
-
--primary-color: #00d4ff;
|
| 4 |
-
--secondary-color: #0891b2;
|
| 5 |
-
--success-color: #0891b2;
|
| 6 |
-
--warning-color: #00d4ff;
|
| 7 |
-
--danger-color: #ef4444;
|
| 8 |
-
--light-color: #f8f9fa;
|
| 9 |
-
--dark-color: #0f172a;
|
| 10 |
-
--gray-100: #f8f9fa;
|
| 11 |
-
--gray-200: #e9ecef;
|
| 12 |
-
--gray-300: #dee2e6;
|
| 13 |
-
--gray-400: #ced4da;
|
| 14 |
-
--gray-500: #adb5bd;
|
| 15 |
-
--gray-600: #6c757d;
|
| 16 |
-
--gray-700: #495057;
|
| 17 |
-
--gray-800: #343a40;
|
| 18 |
-
--gray-900: #212529;
|
| 19 |
-
|
| 20 |
-
--font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 21 |
-
--border-radius: 4px;
|
| 22 |
-
--box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 23 |
-
--transition: all 0.3s ease;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
/* Reset styles */
|
| 27 |
-
* {
|
| 28 |
-
margin: 0;
|
| 29 |
-
padding: 0;
|
| 30 |
-
box-sizing: border-box;
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
html {
|
| 34 |
-
font-size: 16px;
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
body {
|
| 38 |
-
font-family: var(--font-family);
|
| 39 |
-
line-height: 1.6;
|
| 40 |
-
color: var(--gray-800);
|
| 41 |
-
background-color: var(--gray-100);
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
/* Typography */
|
| 45 |
-
h1, h2, h3, h4, h5, h6 {
|
| 46 |
-
margin-bottom: 1rem;
|
| 47 |
-
color: var(--gray-900);
|
| 48 |
-
line-height: 1.2;
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
h1 { font-size: 2.5rem; }
|
| 52 |
-
h2 { font-size: 2rem; }
|
| 53 |
-
h3 { font-size: 1.75rem; }
|
| 54 |
-
h4 { font-size: 1.5rem; }
|
| 55 |
-
h5 { font-size: 1.25rem; }
|
| 56 |
-
h6 { font-size: 1rem; }
|
| 57 |
-
|
| 58 |
-
@media (max-width: 768px) {
|
| 59 |
-
h1 { font-size: 2rem; }
|
| 60 |
-
h2 { font-size: 1.75rem; }
|
| 61 |
-
h3 { font-size: 1.5rem; }
|
| 62 |
-
h4 { font-size: 1.25rem; }
|
| 63 |
-
h5 { font-size: 1.1rem; }
|
| 64 |
-
h6 { font-size: 1rem; }
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
/* Container */
|
| 68 |
-
.container {
|
| 69 |
-
width: 100%;
|
| 70 |
-
max-width: 1200px;
|
| 71 |
-
margin: 0 auto;
|
| 72 |
-
padding: 0 1rem;
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
/* Grid System */
|
| 76 |
-
.grid {
|
| 77 |
-
display: grid;
|
| 78 |
-
gap: 1.5rem;
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
.grid-2 { grid-template-columns: repeat(2, 1fr); }
|
| 82 |
-
.grid-3 { grid-template-columns: repeat(3, 1fr); }
|
| 83 |
-
.grid-4 { grid-template-columns: repeat(4, 1fr); }
|
| 84 |
-
|
| 85 |
-
@media (max-width: 1024px) {
|
| 86 |
-
.grid-4 { grid-template-columns: repeat(3, 1fr); }
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
@media (max-width: 768px) {
|
| 90 |
-
.grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
@media (max-width: 480px) {
|
| 94 |
-
.grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
/* Buttons */
|
| 98 |
-
.btn {
|
| 99 |
-
display: inline-flex;
|
| 100 |
-
align-items: center;
|
| 101 |
-
justify-content: center;
|
| 102 |
-
padding: 0.8rem 1.5rem;
|
| 103 |
-
border: none;
|
| 104 |
-
border-radius: var(--border-radius);
|
| 105 |
-
font-size: 1rem;
|
| 106 |
-
font-weight: 500;
|
| 107 |
-
text-decoration: none;
|
| 108 |
-
cursor: pointer;
|
| 109 |
-
transition: var(--transition);
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
.btn-primary {
|
| 113 |
-
background-color: var(--primary-color);
|
| 114 |
-
color: white;
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
.btn-primary:hover {
|
| 118 |
-
background-color: #d63d40;
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
.btn-secondary {
|
| 122 |
-
background-color: var(--secondary-color);
|
| 123 |
-
color: white;
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
-
.btn-outline {
|
| 127 |
-
background-color: transparent;
|
| 128 |
-
border: 2px solid var(--primary-color);
|
| 129 |
-
color: var(--primary-color);
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
.btn-outline:hover {
|
| 133 |
-
background-color: var(--primary-color);
|
| 134 |
-
color: white;
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
/* Forms */
|
| 138 |
-
.form-group {
|
| 139 |
-
margin-bottom: 1rem;
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
.form-label {
|
| 143 |
-
display: block;
|
| 144 |
-
margin-bottom: 0.5rem;
|
| 145 |
-
color: var(--gray-700);
|
| 146 |
-
font-weight: 500;
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
-
.form-control {
|
| 150 |
-
width: 100%;
|
| 151 |
-
padding: 0.8rem;
|
| 152 |
-
border: 2px solid var(--gray-300);
|
| 153 |
-
border-radius: var(--border-radius);
|
| 154 |
-
font-size: 1rem;
|
| 155 |
-
transition: var(--transition);
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
.form-control:focus {
|
| 159 |
-
border-color: var(--primary-color);
|
| 160 |
-
outline: none;
|
| 161 |
-
}
|
| 162 |
-
|
| 163 |
-
/* Cards */
|
| 164 |
-
.card {
|
| 165 |
-
background-color: white;
|
| 166 |
-
border-radius: var(--border-radius);
|
| 167 |
-
box-shadow: var(--box-shadow);
|
| 168 |
-
overflow: hidden;
|
| 169 |
-
transition: var(--transition);
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
.card:hover {
|
| 173 |
-
transform: translateY(-5px);
|
| 174 |
-
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
| 175 |
-
}
|
| 176 |
-
|
| 177 |
-
.card-img {
|
| 178 |
-
width: 100%;
|
| 179 |
-
height: 200px;
|
| 180 |
-
object-fit: cover;
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
.card-body {
|
| 184 |
-
padding: 1.5rem;
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
.card-title {
|
| 188 |
-
margin-bottom: 0.5rem;
|
| 189 |
-
font-size: 1.25rem;
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
.card-text {
|
| 193 |
-
color: var(--gray-600);
|
| 194 |
-
margin-bottom: 1rem;
|
| 195 |
-
}
|
| 196 |
-
|
| 197 |
-
/* Navigation */
|
| 198 |
-
.navbar {
|
| 199 |
-
background-color: white;
|
| 200 |
-
padding: 1rem;
|
| 201 |
-
box-shadow: var(--box-shadow);
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
.navbar-container {
|
| 205 |
-
display: flex;
|
| 206 |
-
justify-content: space-between;
|
| 207 |
-
align-items: center;
|
| 208 |
-
}
|
| 209 |
-
|
| 210 |
-
.navbar-brand {
|
| 211 |
-
font-size: 1.5rem;
|
| 212 |
-
font-weight: bold;
|
| 213 |
-
color: var(--primary-color);
|
| 214 |
-
text-decoration: none;
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
.navbar-nav {
|
| 218 |
-
display: flex;
|
| 219 |
-
align-items: center;
|
| 220 |
-
gap: 1.5rem;
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
-
.nav-link {
|
| 224 |
-
color: var(--gray-700);
|
| 225 |
-
text-decoration: none;
|
| 226 |
-
transition: var(--transition);
|
| 227 |
-
}
|
| 228 |
-
|
| 229 |
-
.nav-link:hover {
|
| 230 |
-
color: var(--primary-color);
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
.mobile-menu-btn {
|
| 234 |
-
display: none;
|
| 235 |
-
background: none;
|
| 236 |
-
border: none;
|
| 237 |
-
font-size: 1.5rem;
|
| 238 |
-
cursor: pointer;
|
| 239 |
-
color: var(--gray-700);
|
| 240 |
-
}
|
| 241 |
-
|
| 242 |
-
@media (max-width: 768px) {
|
| 243 |
-
.mobile-menu-btn {
|
| 244 |
-
display: block;
|
| 245 |
-
}
|
| 246 |
-
|
| 247 |
-
.navbar-nav {
|
| 248 |
-
display: none;
|
| 249 |
-
position: absolute;
|
| 250 |
-
top: 100%;
|
| 251 |
-
left: 0;
|
| 252 |
-
right: 0;
|
| 253 |
-
background-color: white;
|
| 254 |
-
padding: 1rem;
|
| 255 |
-
box-shadow: var(--box-shadow);
|
| 256 |
-
flex-direction: column;
|
| 257 |
-
gap: 1rem;
|
| 258 |
-
}
|
| 259 |
-
|
| 260 |
-
.navbar-nav.active {
|
| 261 |
-
display: flex;
|
| 262 |
-
}
|
| 263 |
-
}
|
| 264 |
-
|
| 265 |
-
/* Utilities */
|
| 266 |
-
.text-primary { color: var(--primary-color); }
|
| 267 |
-
.text-secondary { color: var(--secondary-color); }
|
| 268 |
-
.text-success { color: var(--success-color); }
|
| 269 |
-
.text-warning { color: var(--warning-color); }
|
| 270 |
-
.text-danger { color: var(--danger-color); }
|
| 271 |
-
|
| 272 |
-
.bg-primary { background-color: var(--primary-color); }
|
| 273 |
-
.bg-secondary { background-color: var(--secondary-color); }
|
| 274 |
-
.bg-success { background-color: var(--success-color); }
|
| 275 |
-
.bg-warning { background-color: var(--warning-color); }
|
| 276 |
-
.bg-danger { background-color: var(--danger-color); }
|
| 277 |
-
|
| 278 |
-
.mt-1 { margin-top: 0.5rem; }
|
| 279 |
-
.mt-2 { margin-top: 1rem; }
|
| 280 |
-
.mt-3 { margin-top: 1.5rem; }
|
| 281 |
-
.mt-4 { margin-top: 2rem; }
|
| 282 |
-
.mt-5 { margin-top: 2.5rem; }
|
| 283 |
-
|
| 284 |
-
.mb-1 { margin-bottom: 0.5rem; }
|
| 285 |
-
.mb-2 { margin-bottom: 1rem; }
|
| 286 |
-
.mb-3 { margin-bottom: 1.5rem; }
|
| 287 |
-
.mb-4 { margin-bottom: 2rem; }
|
| 288 |
-
.mb-5 { margin-bottom: 2.5rem; }
|
| 289 |
-
|
| 290 |
-
.p-1 { padding: 0.5rem; }
|
| 291 |
-
.p-2 { padding: 1rem; }
|
| 292 |
-
.p-3 { padding: 1.5rem; }
|
| 293 |
-
.p-4 { padding: 2rem; }
|
| 294 |
-
.p-5 { padding: 2.5rem; }
|
| 295 |
-
|
| 296 |
-
.text-center { text-align: center; }
|
| 297 |
-
.text-right { text-align: right; }
|
| 298 |
-
.text-left { text-align: left; }
|
| 299 |
-
|
| 300 |
-
.d-flex { display: flex; }
|
| 301 |
-
.align-items-center { align-items: center; }
|
| 302 |
-
.justify-content-center { justify-content: center; }
|
| 303 |
-
.justify-content-between { justify-content: space-between; }
|
| 304 |
-
.flex-column { flex-direction: column; }
|
| 305 |
-
.flex-wrap { flex-wrap: wrap; }
|
| 306 |
-
|
| 307 |
-
.hidden { display: none; }
|
| 308 |
-
.visible { display: block; }
|
| 309 |
-
|
| 310 |
-
@media (max-width: 768px) {
|
| 311 |
-
.hidden-mobile { display: none; }
|
| 312 |
-
}
|
| 313 |
-
|
| 314 |
-
@media (min-width: 769px) {
|
| 315 |
-
.hidden-desktop { display: none; }
|
| 316 |
-
}
|
| 317 |
-
|
| 318 |
-
/* Animations */
|
| 319 |
-
@keyframes fadeIn {
|
| 320 |
-
from { opacity: 0; }
|
| 321 |
-
to { opacity: 1; }
|
| 322 |
-
}
|
| 323 |
-
|
| 324 |
-
@keyframes slideIn {
|
| 325 |
-
from { transform: translateY(-20px); opacity: 0; }
|
| 326 |
-
to { transform: translateY(0); opacity: 1; }
|
| 327 |
-
}
|
| 328 |
-
|
| 329 |
-
.fade-in {
|
| 330 |
-
animation: fadeIn 0.3s ease-in-out;
|
| 331 |
-
}
|
| 332 |
-
|
| 333 |
-
.slide-in {
|
| 334 |
-
animation: slideIn 0.3s ease-in-out;
|
| 335 |
-
}
|
| 336 |
-
/* === PRODUCTION FONT SIZES (Temu/AliExpress/Amazon style) === */
|
| 337 |
-
/* Smaller, denser text for better information density */
|
| 338 |
-
html { font-size: 14px; } /* Base size down from 16px */
|
| 339 |
-
body { font-size: 0.875rem; } /* 14px */
|
| 340 |
-
h1 { font-size: 1.25rem; } /* 20px — was 2.5rem */
|
| 341 |
-
h2 { font-size: 1.125rem; } /* 18px — was 2rem */
|
| 342 |
-
h3 { font-size: 1rem; } /* 16px — was 1.75rem */
|
| 343 |
-
h4 { font-size: 0.9375rem; } /* 15px — was 1.5rem */
|
| 344 |
-
h5 { font-size: 0.875rem; } /* 14px — was 1.25rem */
|
| 345 |
-
h6 { font-size: 0.8125rem; } /* 13px — was 1rem */
|
| 346 |
-
p, span, div, a, button, input, select, textarea { font-size: 0.8125rem; } /* 13px */
|
| 347 |
-
.text-xs { font-size: 0.625rem !important; } /* 10px — for badges, meta */
|
| 348 |
-
.text-sm { font-size: 0.75rem !important; } /* 12px */
|
| 349 |
-
.text-base { font-size: 0.8125rem !important; } /* 13px */
|
| 350 |
-
.text-lg { font-size: 0.9375rem !important; } /* 15px */
|
| 351 |
-
.text-xl { font-size: 1.0625rem !important; } /* 17px */
|
| 352 |
-
.text-2xl { font-size: 1.25rem !important; } /* 20px */
|
| 353 |
-
.text-3xl { font-size: 1.5rem !important; } /* 24px */
|
| 354 |
-
|
| 355 |
-
@media (max-width: 768px) {
|
| 356 |
-
html { font-size: 13px; }
|
| 357 |
-
h1 { font-size: 1.125rem; }
|
| 358 |
-
h2 { font-size: 1rem; }
|
| 359 |
-
h3 { font-size: 0.9375rem; }
|
| 360 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
css/index.css
DELETED
|
@@ -1,747 +0,0 @@
|
|
| 1 |
-
/* ============================================================
|
| 2 |
-
EESHA MARKETPLACE - PROFESSIONAL CSS STYLESHEET
|
| 3 |
-
Extracted from Site123 template with custom refinements
|
| 4 |
-
Dark theme with beige accents, Caladea typography
|
| 5 |
-
============================================================ */
|
| 6 |
-
/* Fonts (Caladea) */
|
| 7 |
-
@font-face {
|
| 8 |
-
font-family: 'Caladea';
|
| 9 |
-
font-style: italic;
|
| 10 |
-
font-weight: 400;
|
| 11 |
-
font-display: swap;
|
| 12 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJExBugZ7AAjhybUvR1N9vo3huR_DaS8.woff2) format('woff2');
|
| 13 |
-
}
|
| 14 |
-
@font-face {
|
| 15 |
-
font-family: 'Caladea';
|
| 16 |
-
font-style: italic;
|
| 17 |
-
font-weight: 400;
|
| 18 |
-
font-display: swap;
|
| 19 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJExBugZ7AAjhybUvR1N-Po3huR_DQ.woff2) format('woff2');
|
| 20 |
-
}
|
| 21 |
-
@font-face {
|
| 22 |
-
font-family: 'Caladea';
|
| 23 |
-
font-style: italic;
|
| 24 |
-
font-weight: 700;
|
| 25 |
-
font-display: swap;
|
| 26 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE0BugZ7AAjhybUvR1FQ98ipcZyJ561DbZg.woff2) format('woff2');
|
| 27 |
-
}
|
| 28 |
-
@font-face {
|
| 29 |
-
font-family: 'Caladea';
|
| 30 |
-
font-style: italic;
|
| 31 |
-
font-weight: 700;
|
| 32 |
-
font-display: swap;
|
| 33 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE0BugZ7AAjhybUvR1FQ98iq8ZyJ561DQ.woff2) format('woff2');
|
| 34 |
-
}
|
| 35 |
-
@font-face {
|
| 36 |
-
font-family: 'Caladea';
|
| 37 |
-
font-style: normal;
|
| 38 |
-
font-weight: 400;
|
| 39 |
-
font-display: swap;
|
| 40 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJEzBugZ7AAjhybUvRZ9-vAwpOZPDA.woff2) format('woff2');
|
| 41 |
-
}
|
| 42 |
-
@font-face {
|
| 43 |
-
font-family: 'Caladea';
|
| 44 |
-
font-style: normal;
|
| 45 |
-
font-weight: 400;
|
| 46 |
-
font-display: swap;
|
| 47 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJEzBugZ7AAjhybUvRh9-vAwpOY.woff2) format('woff2');
|
| 48 |
-
}
|
| 49 |
-
@font-face {
|
| 50 |
-
font-family: 'Caladea';
|
| 51 |
-
font-style: normal;
|
| 52 |
-
font-weight: 700;
|
| 53 |
-
font-display: swap;
|
| 54 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE2BugZ7AAjhybUtaNY79MSqcx1BZyFDA.woff2) format('woff2');
|
| 55 |
-
}
|
| 56 |
-
@font-face {
|
| 57 |
-
font-family: 'Caladea';
|
| 58 |
-
font-style: normal;
|
| 59 |
-
font-weight: 700;
|
| 60 |
-
font-display: swap;
|
| 61 |
-
src: url(https://fonts.gstatic.com/s/caladea/v8/kJE2BugZ7AAjhybUtaNY790Sqcx1BZw.woff2) format('woff2');
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
/* Top-level CSS variables (copied from index copy.html) */
|
| 65 |
-
:root {
|
| 66 |
-
--global_main_color: #f8dcbf;
|
| 67 |
-
--global_main_color_btn_text_color: #5c340a;
|
| 68 |
-
--home_text_color: #ffffff;
|
| 69 |
-
--home_third_background_color: #000000;
|
| 70 |
-
--home_custom_image_size: px;
|
| 71 |
-
--home_custom_image_width: px;
|
| 72 |
-
--home_text_size_px: 71px;
|
| 73 |
-
--home_text_size_2_px: 45px;
|
| 74 |
-
--slogan_text_size_px: 22px;
|
| 75 |
-
--home_text_size: 71;
|
| 76 |
-
--home_text_size_2: 45;
|
| 77 |
-
--slogan_text_size: 22;
|
| 78 |
-
--home_text_size_px_media: 71px;
|
| 79 |
-
--home_text_size_2_px_media: 45px;
|
| 80 |
-
--slogan_text_size_px_media: 22px;
|
| 81 |
-
--layout_text_align: center;
|
| 82 |
-
--layout_text_align_rtl: center;
|
| 83 |
-
--layout_text_box_width: 100%;
|
| 84 |
-
--layout_left_side_width: 50%;
|
| 85 |
-
--layout_left_side_width_vh: 50vh;
|
| 86 |
-
--homepage_layout_height: 75%;
|
| 87 |
-
--homepage_layout_height_vh: 75vh;
|
| 88 |
-
--homepage_layout_height_menu_space: 0px;
|
| 89 |
-
--mainNavMobileHeight: 0px;
|
| 90 |
-
--homepage_layout_height_opacity_space_top: 0px;
|
| 91 |
-
--homepage_layout_height_opacity_space_bottom: 0px;
|
| 92 |
-
--mobileMenuFontSize: 20px;
|
| 93 |
-
--mobileMenuPagesSpace: 5px;
|
| 94 |
-
--homepageShapeDividerList_Size: 17%;
|
| 95 |
-
--layout_bottom_spacing: 0px;
|
| 96 |
-
--window-height: 100vh;
|
| 97 |
-
--window-width: 100vw;
|
| 98 |
-
--menu_font_size: 17px;
|
| 99 |
-
--menu_pages_space: 3px;
|
| 100 |
-
--menu_pages_side_padding: 20px;
|
| 101 |
-
--menu_pages_letter_spacing: 1.3px;
|
| 102 |
-
--menu_pages_word_spacing: 0px;
|
| 103 |
-
--menu_thin_border: #1b262c;
|
| 104 |
-
--mobileMenuTextAlign: center;
|
| 105 |
-
--menu_text_color: #fae6d1;
|
| 106 |
-
--menu_text_hover_color: #fae6d1;
|
| 107 |
-
--menu_color: #1b262c;
|
| 108 |
-
--modules_color_text: #ffffff;
|
| 109 |
-
--modules_color_text_second: #ffffff;
|
| 110 |
-
--inside_modules_color_text: #ffffff;
|
| 111 |
-
--sectionsPadding: 100px;
|
| 112 |
-
--home_text_top_space_1: 0px;
|
| 113 |
-
--home_text_bottom_space_1: 16px;
|
| 114 |
-
--home_text_top_space_2: 0px;
|
| 115 |
-
--home_text_bottom_space_2: 30px;
|
| 116 |
-
--home_text_top_space_3: 0px;
|
| 117 |
-
--home_text_bottom_space_3: 20px;
|
| 118 |
-
--homepage_padding_top: px;
|
| 119 |
-
--homepage_padding_bottom: px;
|
| 120 |
-
--homepage_main_goal_margin_top: 0px;
|
| 121 |
-
--homepage_main_goal_margin_bottom: 0px;
|
| 122 |
-
--homepage_second_goal_margin_top: 0px;
|
| 123 |
-
--homepage_second_goal_margin_bottom: 0px;
|
| 124 |
-
--modules_color_second: #000000;
|
| 125 |
-
--modules_color: #1b262c;
|
| 126 |
-
--footer_back: #1b262c;
|
| 127 |
-
--footer_text_hover: #f8dcbf;
|
| 128 |
-
--footer_links_color: #f8dcbf;
|
| 129 |
-
--footer_text: #ffffff;
|
| 130 |
-
--home_background_color: #000000;
|
| 131 |
-
--inside_modules_color_box: #1b262c;
|
| 132 |
-
--inside_modules_color_text_box: #ffffff;
|
| 133 |
-
--module_separate_border_color: ;
|
| 134 |
-
--modules_color_box: #000000;
|
| 135 |
-
--modules_color_second_box: #1b262c;
|
| 136 |
-
--modules_color_text_second_box: #ffffff;
|
| 137 |
-
--inside_modules_color: #000000;
|
| 138 |
-
--modules_color_text_box: #ffffff;
|
| 139 |
-
--font_logo: ;
|
| 140 |
-
--font_menu: Caladea;
|
| 141 |
-
--global_font: Caladea;
|
| 142 |
-
--font_slogan: ;
|
| 143 |
-
--font_slogan_2: ;
|
| 144 |
-
--font_second_slogan: ;
|
| 145 |
-
--font_modules_header: Caladea;
|
| 146 |
-
--sticky_menu: 1;
|
| 147 |
-
--scrollHeaderSize: 0px;
|
| 148 |
-
--modules_color_section_main: #f8dcbf;
|
| 149 |
-
--modules_color_section_btn_text: #5c340a;
|
| 150 |
-
--modules_color_second_section_main: #f8dcbf;
|
| 151 |
-
--modules_color_second_section_btn_text: #5c340a;
|
| 152 |
-
--inside_modules_color_section_box_main: #f8dcbf;
|
| 153 |
-
--inside_modules_color_section_btn_text: #5c340a;
|
| 154 |
-
--line_height_modules_header: 1.25;
|
| 155 |
-
--global_line_height: 1.25;
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
/* Base styles */
|
| 159 |
-
html, body {
|
| 160 |
-
height: 100%;
|
| 161 |
-
margin: 0;
|
| 162 |
-
padding: 0;
|
| 163 |
-
background: var(--home_background_color, #000);
|
| 164 |
-
color: var(--modules_color_text, #fff);
|
| 165 |
-
font-family: var(--global_font, Caladea), system-ui, -apple-system, serif;
|
| 166 |
-
-webkit-font-smoothing: antialiased;
|
| 167 |
-
-moz-osx-font-smoothing: grayscale;
|
| 168 |
-
}
|
| 169 |
-
|
| 170 |
-
/* Header styling - clean Site123 nav with polish */
|
| 171 |
-
.header-s123 {
|
| 172 |
-
background: linear-gradient(180deg, var(--menu_color, #1b262c) 0%, rgba(27, 38, 44, 0.98) 100%);
|
| 173 |
-
color: var(--menu_text_color, #fae6d1);
|
| 174 |
-
display: flex;
|
| 175 |
-
align-items: center;
|
| 176 |
-
height: auto;
|
| 177 |
-
padding: 1.2rem 2rem;
|
| 178 |
-
position: fixed;
|
| 179 |
-
top: 0;
|
| 180 |
-
left: 0;
|
| 181 |
-
right: 0;
|
| 182 |
-
z-index: 50;
|
| 183 |
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
| 184 |
-
border-bottom: 2px solid rgba(248, 220, 191, 0.1);
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
.navbar-s123 {
|
| 188 |
-
display: flex;
|
| 189 |
-
align-items: center;
|
| 190 |
-
justify-content: space-between;
|
| 191 |
-
width: 100%;
|
| 192 |
-
max-width: 1400px;
|
| 193 |
-
margin: 0 auto;
|
| 194 |
-
gap: 3rem;
|
| 195 |
-
}
|
| 196 |
-
|
| 197 |
-
.nav-brand {
|
| 198 |
-
flex: 0 0 auto;
|
| 199 |
-
display: flex;
|
| 200 |
-
align-items: center;
|
| 201 |
-
gap: 0.75rem;
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
.site-title {
|
| 205 |
-
font-family: var(--global_font, Caladea), serif;
|
| 206 |
-
font-size: 1.6rem;
|
| 207 |
-
font-weight: 700;
|
| 208 |
-
color: var(--modules_color_section_main, #f8dcbf);
|
| 209 |
-
margin: 0;
|
| 210 |
-
letter-spacing: 1.2px;
|
| 211 |
-
text-transform: uppercase;
|
| 212 |
-
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
| 213 |
-
}
|
| 214 |
-
|
| 215 |
-
.nav-menu {
|
| 216 |
-
display: flex;
|
| 217 |
-
align-items: center;
|
| 218 |
-
gap: 3rem;
|
| 219 |
-
flex: 1;
|
| 220 |
-
justify-content: center;
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
-
.nav-link {
|
| 224 |
-
font-family: var(--font_menu, Caladea), serif;
|
| 225 |
-
color: var(--menu_text_color, #fae6d1);
|
| 226 |
-
text-decoration: none;
|
| 227 |
-
font-size: 0.98rem;
|
| 228 |
-
font-weight: 600;
|
| 229 |
-
letter-spacing: 0.8px;
|
| 230 |
-
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
| 231 |
-
border-bottom: 2px solid transparent;
|
| 232 |
-
padding-bottom: 6px;
|
| 233 |
-
text-transform: uppercase;
|
| 234 |
-
}
|
| 235 |
-
|
| 236 |
-
.nav-link:hover {
|
| 237 |
-
color: var(--modules_color_section_main, #f8dcbf);
|
| 238 |
-
border-bottom-color: var(--modules_color_section_main, #f8dcbf);
|
| 239 |
-
transform: translateY(-1px);
|
| 240 |
-
}
|
| 241 |
-
|
| 242 |
-
.nav-actions {
|
| 243 |
-
flex: 0 0 auto;
|
| 244 |
-
display: flex;
|
| 245 |
-
align-items: center;
|
| 246 |
-
gap: 1.75rem;
|
| 247 |
-
}
|
| 248 |
-
|
| 249 |
-
.cart-link {
|
| 250 |
-
color: var(--menu_text_color, #fae6d1);
|
| 251 |
-
font-size: 1.3rem;
|
| 252 |
-
position: relative;
|
| 253 |
-
text-decoration: none;
|
| 254 |
-
transition: all 0.3s ease;
|
| 255 |
-
display: flex;
|
| 256 |
-
align-items: center;
|
| 257 |
-
}
|
| 258 |
-
|
| 259 |
-
.cart-link:hover {
|
| 260 |
-
color: var(--modules_color_section_main, #f8dcbf);
|
| 261 |
-
transform: scale(1.1);
|
| 262 |
-
}
|
| 263 |
-
|
| 264 |
-
.cart-badge {
|
| 265 |
-
position: absolute;
|
| 266 |
-
top: -6px;
|
| 267 |
-
right: -10px;
|
| 268 |
-
background: linear-gradient(135deg, var(--modules_color_section_main, #f8dcbf) 0%, #f0d4b0 100%);
|
| 269 |
-
color: var(--modules_color_section_btn_text, #5c340a);
|
| 270 |
-
font-size: 0.7rem;
|
| 271 |
-
font-weight: 700;
|
| 272 |
-
width: 20px;
|
| 273 |
-
height: 20px;
|
| 274 |
-
border-radius: 50%;
|
| 275 |
-
display: flex;
|
| 276 |
-
align-items: center;
|
| 277 |
-
justify-content: center;
|
| 278 |
-
box-shadow: 0 2px 6px rgba(248, 220, 191, 0.3);
|
| 279 |
-
animation: pulse-badge 2s infinite;
|
| 280 |
-
}
|
| 281 |
-
|
| 282 |
-
@keyframes pulse-badge {
|
| 283 |
-
0%, 100% {
|
| 284 |
-
box-shadow: 0 2px 6px rgba(248, 220, 191, 0.3);
|
| 285 |
-
}
|
| 286 |
-
50% {
|
| 287 |
-
box-shadow: 0 2px 10px rgba(248, 220, 191, 0.5);
|
| 288 |
-
}
|
| 289 |
-
}
|
| 290 |
-
|
| 291 |
-
/* Main content */
|
| 292 |
-
main {
|
| 293 |
-
margin-top: 70px;
|
| 294 |
-
}
|
| 295 |
-
|
| 296 |
-
section {
|
| 297 |
-
padding: 3rem 2rem;
|
| 298 |
-
background: var(--home_background_color, #000);
|
| 299 |
-
color: var(--modules_color_text, #fff);
|
| 300 |
-
}
|
| 301 |
-
|
| 302 |
-
/* Section headers */
|
| 303 |
-
.page-header-wrap {
|
| 304 |
-
text-align: center;
|
| 305 |
-
margin-bottom: 2rem;
|
| 306 |
-
}
|
| 307 |
-
|
| 308 |
-
.s123-page-header {
|
| 309 |
-
font-family: var(--font_modules_header, Caladea), serif;
|
| 310 |
-
font-size: 1.8rem;
|
| 311 |
-
font-weight: 700;
|
| 312 |
-
color: var(--modules_color_text, #fff);
|
| 313 |
-
text-transform: uppercase;
|
| 314 |
-
letter-spacing: 1.5px;
|
| 315 |
-
margin: 0 0 1rem 0;
|
| 316 |
-
line-height: var(--line_height_modules_header, 1.25);
|
| 317 |
-
}
|
| 318 |
-
|
| 319 |
-
.page-header-wrap .small {
|
| 320 |
-
display: block;
|
| 321 |
-
width: 80px;
|
| 322 |
-
height: 2px;
|
| 323 |
-
background: var(--modules_color_section_main, #f8dcbf);
|
| 324 |
-
margin: 0 auto;
|
| 325 |
-
border: none;
|
| 326 |
-
}
|
| 327 |
-
|
| 328 |
-
/* Container utilities */
|
| 329 |
-
.container {
|
| 330 |
-
max-width: 1200px;
|
| 331 |
-
margin: 0 auto;
|
| 332 |
-
padding: 0 2rem;
|
| 333 |
-
}
|
| 334 |
-
|
| 335 |
-
.container-fluid {
|
| 336 |
-
width: 100%;
|
| 337 |
-
padding: 0 2rem;
|
| 338 |
-
}
|
| 339 |
-
|
| 340 |
-
/* Collection/Category boxes */
|
| 341 |
-
.collection-container {
|
| 342 |
-
display: grid;
|
| 343 |
-
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 344 |
-
gap: 2rem;
|
| 345 |
-
padding: 2rem 0;
|
| 346 |
-
}
|
| 347 |
-
|
| 348 |
-
.e-c-box {
|
| 349 |
-
position: relative;
|
| 350 |
-
}
|
| 351 |
-
|
| 352 |
-
.box {
|
| 353 |
-
position: relative;
|
| 354 |
-
overflow: hidden;
|
| 355 |
-
border-radius: 12px;
|
| 356 |
-
background: var(--inside_modules_color_box, #1b262c);
|
| 357 |
-
transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
| 358 |
-
cursor: pointer;
|
| 359 |
-
border: 1px solid rgba(248, 220, 191, 0.1);
|
| 360 |
-
}
|
| 361 |
-
|
| 362 |
-
.box:hover {
|
| 363 |
-
transform: translateY(-8px);
|
| 364 |
-
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
|
| 365 |
-
border-color: rgba(248, 220, 191, 0.3);
|
| 366 |
-
}
|
| 367 |
-
|
| 368 |
-
.image-container {
|
| 369 |
-
display: block;
|
| 370 |
-
width: 100%;
|
| 371 |
-
text-decoration: none;
|
| 372 |
-
color: inherit;
|
| 373 |
-
}
|
| 374 |
-
|
| 375 |
-
.collection-image {
|
| 376 |
-
width: 100%;
|
| 377 |
-
height: 280px;
|
| 378 |
-
background-size: cover;
|
| 379 |
-
background-position: center;
|
| 380 |
-
display: flex;
|
| 381 |
-
align-items: flex-end;
|
| 382 |
-
position: relative;
|
| 383 |
-
transition: transform 0.4s ease;
|
| 384 |
-
}
|
| 385 |
-
|
| 386 |
-
.box:hover .collection-image {
|
| 387 |
-
transform: scale(1.05);
|
| 388 |
-
}
|
| 389 |
-
|
| 390 |
-
.s123-image-ratio-1-1 {
|
| 391 |
-
aspect-ratio: 1 / 1;
|
| 392 |
-
height: auto;
|
| 393 |
-
}
|
| 394 |
-
|
| 395 |
-
.title-container {
|
| 396 |
-
padding: 1.5rem;
|
| 397 |
-
background: rgba(0,0,0,0.8);
|
| 398 |
-
color: var(--inside_modules_color_text_box, #fff);
|
| 399 |
-
text-align: center;
|
| 400 |
-
}
|
| 401 |
-
|
| 402 |
-
.title {
|
| 403 |
-
font-family: var(--font_modules_header, Caladea), serif;
|
| 404 |
-
font-size: 1.3rem;
|
| 405 |
-
font-weight: 700;
|
| 406 |
-
color: var(--modules_color_text_box, #fff);
|
| 407 |
-
margin: 0 0 0.5rem 0;
|
| 408 |
-
text-transform: uppercase;
|
| 409 |
-
letter-spacing: 1px;
|
| 410 |
-
}
|
| 411 |
-
|
| 412 |
-
.count {
|
| 413 |
-
font-size: 0.85rem;
|
| 414 |
-
color: var(--modules_color_text_box, #fff);
|
| 415 |
-
opacity: 0.85;
|
| 416 |
-
}
|
| 417 |
-
|
| 418 |
-
.count-txt {
|
| 419 |
-
margin-left: 0.25rem;
|
| 420 |
-
}
|
| 421 |
-
|
| 422 |
-
/* Services section */
|
| 423 |
-
.services-category {
|
| 424 |
-
display: grid;
|
| 425 |
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 426 |
-
gap: 2rem;
|
| 427 |
-
}
|
| 428 |
-
|
| 429 |
-
.service-item {
|
| 430 |
-
text-align: center;
|
| 431 |
-
padding: 2rem 1rem;
|
| 432 |
-
}
|
| 433 |
-
|
| 434 |
-
.service-item h4 {
|
| 435 |
-
font-family: var(--font_modules_header, Caladea), serif;
|
| 436 |
-
font-size: 1.1rem;
|
| 437 |
-
color: var(--modules_color_text, #fff);
|
| 438 |
-
margin: 1rem 0 0.75rem 0;
|
| 439 |
-
}
|
| 440 |
-
|
| 441 |
-
.service-item p {
|
| 442 |
-
font-size: 0.95rem;
|
| 443 |
-
line-height: 1.6;
|
| 444 |
-
color: var(--modules_color_text, #fff);
|
| 445 |
-
opacity: 0.9;
|
| 446 |
-
}
|
| 447 |
-
|
| 448 |
-
/* Testimonials */
|
| 449 |
-
.testimonials-category {
|
| 450 |
-
display: grid;
|
| 451 |
-
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 452 |
-
gap: 2rem;
|
| 453 |
-
}
|
| 454 |
-
|
| 455 |
-
.testimonials-text-container .box {
|
| 456 |
-
background: var(--inside_modules_color_box, #1b262c);
|
| 457 |
-
padding: 1.5rem;
|
| 458 |
-
border-radius: 8px;
|
| 459 |
-
}
|
| 460 |
-
|
| 461 |
-
.testimonials-text-container blockquote {
|
| 462 |
-
margin: 0 0 1rem 0;
|
| 463 |
-
}
|
| 464 |
-
|
| 465 |
-
.testimonials-text-container p {
|
| 466 |
-
font-size: 0.95rem;
|
| 467 |
-
line-height: 1.6;
|
| 468 |
-
color: var(--modules_color_text_box, #fff);
|
| 469 |
-
margin: 0 0 1rem 0;
|
| 470 |
-
font-style: italic;
|
| 471 |
-
}
|
| 472 |
-
|
| 473 |
-
.testimonials-img {
|
| 474 |
-
width: 100%;
|
| 475 |
-
height: 150px;
|
| 476 |
-
margin-bottom: 1rem;
|
| 477 |
-
border-radius: 8px;
|
| 478 |
-
overflow: hidden;
|
| 479 |
-
}
|
| 480 |
-
|
| 481 |
-
.img-bg {
|
| 482 |
-
width: 100%;
|
| 483 |
-
height: 100%;
|
| 484 |
-
background-size: cover;
|
| 485 |
-
background-position: center;
|
| 486 |
-
}
|
| 487 |
-
|
| 488 |
-
.testimonials-wrap {
|
| 489 |
-
text-align: center;
|
| 490 |
-
}
|
| 491 |
-
|
| 492 |
-
.testimonials-name {
|
| 493 |
-
font-family: var(--font_modules_header, Caladea), serif;
|
| 494 |
-
font-size: 1rem;
|
| 495 |
-
font-weight: 700;
|
| 496 |
-
color: var(--modules_color_text_box, #fff);
|
| 497 |
-
}
|
| 498 |
-
|
| 499 |
-
.testimonials-role {
|
| 500 |
-
font-size: 0.85rem;
|
| 501 |
-
color: var(--modules_color_section_main, #f8dcbf);
|
| 502 |
-
}
|
| 503 |
-
|
| 504 |
-
/* Contact form */
|
| 505 |
-
.s123-module-contact-form {
|
| 506 |
-
max-width: 800px;
|
| 507 |
-
margin: 0 auto;
|
| 508 |
-
}
|
| 509 |
-
|
| 510 |
-
.form-group {
|
| 511 |
-
margin-bottom: 1rem;
|
| 512 |
-
}
|
| 513 |
-
|
| 514 |
-
.form-control {
|
| 515 |
-
width: 100%;
|
| 516 |
-
padding: 0.75rem;
|
| 517 |
-
border: 1px solid rgba(248, 220, 191, 0.3);
|
| 518 |
-
border-radius: 4px;
|
| 519 |
-
background: rgba(0,0,0,0.3);
|
| 520 |
-
color: var(--modules_color_text_box, #fff);
|
| 521 |
-
font-family: inherit;
|
| 522 |
-
font-size: 0.95rem;
|
| 523 |
-
}
|
| 524 |
-
|
| 525 |
-
.form-control:focus {
|
| 526 |
-
outline: none;
|
| 527 |
-
border-color: var(--modules_color_section_main, #f8dcbf);
|
| 528 |
-
background: rgba(0,0,0,0.5);
|
| 529 |
-
}
|
| 530 |
-
|
| 531 |
-
.form-control::placeholder {
|
| 532 |
-
color: rgba(248, 220, 191, 0.5);
|
| 533 |
-
}
|
| 534 |
-
|
| 535 |
-
.btn {
|
| 536 |
-
display: inline-block;
|
| 537 |
-
padding: 0.85rem 2.5rem;
|
| 538 |
-
border-radius: 6px;
|
| 539 |
-
background: linear-gradient(135deg, var(--modules_color_section_main, #f8dcbf) 0%, #f0d4b0 100%);
|
| 540 |
-
color: var(--modules_color_section_btn_text, #5c340a);
|
| 541 |
-
border: 2px solid var(--modules_color_section_main, #f8dcbf);
|
| 542 |
-
cursor: pointer;
|
| 543 |
-
font-family: var(--font_modules_header, Caladea), serif;
|
| 544 |
-
font-size: 0.98rem;
|
| 545 |
-
font-weight: 700;
|
| 546 |
-
letter-spacing: 0.8px;
|
| 547 |
-
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
| 548 |
-
box-shadow: 0 4px 12px rgba(248, 220, 191, 0.2);
|
| 549 |
-
text-transform: uppercase;
|
| 550 |
-
}
|
| 551 |
-
|
| 552 |
-
.btn:hover {
|
| 553 |
-
background: linear-gradient(135deg, #f0d4b0 0%, var(--modules_color_section_main, #f8dcbf) 100%);
|
| 554 |
-
transform: translateY(-3px);
|
| 555 |
-
box-shadow: 0 6px 20px rgba(248, 220, 191, 0.35);
|
| 556 |
-
}
|
| 557 |
-
|
| 558 |
-
.btn:active {
|
| 559 |
-
transform: translateY(-1px);
|
| 560 |
-
}
|
| 561 |
-
|
| 562 |
-
.btn-primary {
|
| 563 |
-
background: linear-gradient(135deg, var(--modules_color_section_main, #f8dcbf) 0%, #f0d4b0 100%);
|
| 564 |
-
color: var(--modules_color_section_btn_text, #5c340a);
|
| 565 |
-
}
|
| 566 |
-
|
| 567 |
-
/* Footer */
|
| 568 |
-
footer {
|
| 569 |
-
background: linear-gradient(180deg, var(--footer_back, #1b262c) 0%, rgba(27, 38, 44, 0.95) 100%);
|
| 570 |
-
color: var(--footer_text, #fff);
|
| 571 |
-
padding: 3.5rem 2rem 2rem 2rem;
|
| 572 |
-
margin-top: 3rem;
|
| 573 |
-
border-top: 2px solid rgba(248, 220, 191, 0.1);
|
| 574 |
-
}
|
| 575 |
-
|
| 576 |
-
footer .container {
|
| 577 |
-
max-width: 1200px;
|
| 578 |
-
margin: 0 auto;
|
| 579 |
-
}
|
| 580 |
-
|
| 581 |
-
footer .grid {
|
| 582 |
-
display: grid;
|
| 583 |
-
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
| 584 |
-
gap: 2.5rem;
|
| 585 |
-
margin-bottom: 2.5rem;
|
| 586 |
-
}
|
| 587 |
-
|
| 588 |
-
footer h3 {
|
| 589 |
-
font-family: var(--font_modules_header, Caladea), serif;
|
| 590 |
-
font-size: 1.15rem;
|
| 591 |
-
font-weight: 700;
|
| 592 |
-
color: var(--modules_color_section_main, #f8dcbf);
|
| 593 |
-
margin: 0 0 1.25rem 0;
|
| 594 |
-
text-transform: uppercase;
|
| 595 |
-
letter-spacing: 1px;
|
| 596 |
-
}
|
| 597 |
-
|
| 598 |
-
footer p {
|
| 599 |
-
font-size: 0.92rem;
|
| 600 |
-
line-height: 1.7;
|
| 601 |
-
color: var(--footer_text, #fff);
|
| 602 |
-
opacity: 0.85;
|
| 603 |
-
margin: 0;
|
| 604 |
-
}
|
| 605 |
-
|
| 606 |
-
footer ul {
|
| 607 |
-
list-style: none;
|
| 608 |
-
padding: 0;
|
| 609 |
-
margin: 0;
|
| 610 |
-
}
|
| 611 |
-
|
| 612 |
-
footer li {
|
| 613 |
-
margin-bottom: 0.85rem;
|
| 614 |
-
}
|
| 615 |
-
|
| 616 |
-
footer a {
|
| 617 |
-
color: var(--footer_links_color, #f8dcbf);
|
| 618 |
-
text-decoration: none;
|
| 619 |
-
font-size: 0.92rem;
|
| 620 |
-
transition: all 0.3s ease;
|
| 621 |
-
}
|
| 622 |
-
|
| 623 |
-
footer a:hover {
|
| 624 |
-
color: var(--footer_text_hover, #fff);
|
| 625 |
-
text-decoration: underline;
|
| 626 |
-
margin-left: 4px;
|
| 627 |
-
}
|
| 628 |
-
|
| 629 |
-
.footer-icons {
|
| 630 |
-
display: flex;
|
| 631 |
-
gap: 1.2rem;
|
| 632 |
-
}
|
| 633 |
-
|
| 634 |
-
.footer-icons a {
|
| 635 |
-
display: inline-flex;
|
| 636 |
-
align-items: center;
|
| 637 |
-
justify-content: center;
|
| 638 |
-
width: 40px;
|
| 639 |
-
height: 40px;
|
| 640 |
-
background: rgba(248, 220, 191, 0.12);
|
| 641 |
-
border-radius: 50%;
|
| 642 |
-
font-size: 1.2rem;
|
| 643 |
-
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
| 644 |
-
border: 1px solid rgba(248, 220, 191, 0.2);
|
| 645 |
-
}
|
| 646 |
-
|
| 647 |
-
.footer-icons a:hover {
|
| 648 |
-
background: linear-gradient(135deg, var(--modules_color_section_main, #f8dcbf) 0%, #f0d4b0 100%);
|
| 649 |
-
color: var(--modules_color_section_btn_text, #5c340a);
|
| 650 |
-
transform: translateY(-3px);
|
| 651 |
-
box-shadow: 0 8px 20px rgba(248, 220, 191, 0.25);
|
| 652 |
-
}
|
| 653 |
-
|
| 654 |
-
.footer-bottom {
|
| 655 |
-
border-top: 1px solid rgba(248, 220, 191, 0.15);
|
| 656 |
-
padding-top: 1.75rem;
|
| 657 |
-
text-align: center;
|
| 658 |
-
font-size: 0.88rem;
|
| 659 |
-
color: var(--footer_text, #fff);
|
| 660 |
-
opacity: 0.75;
|
| 661 |
-
letter-spacing: 0.3px;
|
| 662 |
-
}
|
| 663 |
-
|
| 664 |
-
/* Utility classes */
|
| 665 |
-
.flex {
|
| 666 |
-
display: flex;
|
| 667 |
-
}
|
| 668 |
-
|
| 669 |
-
.flex-wrap {
|
| 670 |
-
flex-wrap: wrap;
|
| 671 |
-
}
|
| 672 |
-
|
| 673 |
-
.gap-2 {
|
| 674 |
-
gap: 0.5rem;
|
| 675 |
-
}
|
| 676 |
-
|
| 677 |
-
.justify-center {
|
| 678 |
-
justify-content: center;
|
| 679 |
-
}
|
| 680 |
-
|
| 681 |
-
.mb-4 {
|
| 682 |
-
margin-bottom: 1rem;
|
| 683 |
-
}
|
| 684 |
-
|
| 685 |
-
.mt-6 {
|
| 686 |
-
margin-top: 1.5rem;
|
| 687 |
-
}
|
| 688 |
-
|
| 689 |
-
.w-full {
|
| 690 |
-
width: 100%;
|
| 691 |
-
}
|
| 692 |
-
|
| 693 |
-
.title-container {
|
| 694 |
-
padding: 1.75rem;
|
| 695 |
-
background: linear-gradient(180deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.95) 100%);
|
| 696 |
-
color: var(--inside_modules_color_text_box, #fff);
|
| 697 |
-
text-align: center;
|
| 698 |
-
}
|
| 699 |
-
|
| 700 |
-
.title {
|
| 701 |
-
font-family: var(--font_modules_header, Caladea), serif;
|
| 702 |
-
font-size: 1.35rem;
|
| 703 |
-
font-weight: 700;
|
| 704 |
-
color: var(--modules_color_section_main, #f8dcbf);
|
| 705 |
-
margin: 0 0 0.5rem 0;
|
| 706 |
-
text-transform: uppercase;
|
| 707 |
-
letter-spacing: 1.2px;
|
| 708 |
-
transition: color 0.3s ease;
|
| 709 |
-
}
|
| 710 |
-
|
| 711 |
-
.box:hover .title {
|
| 712 |
-
color: #fff;
|
| 713 |
-
}
|
| 714 |
-
|
| 715 |
-
.count {
|
| 716 |
-
font-size: 0.88rem;
|
| 717 |
-
color: var(--modules_color_text_box, #fff);
|
| 718 |
-
opacity: 0.8;
|
| 719 |
-
letter-spacing: 0.5px;
|
| 720 |
-
.col-sm-6 {
|
| 721 |
-
grid-column: span 6;
|
| 722 |
-
}
|
| 723 |
-
|
| 724 |
-
/* Responsive */
|
| 725 |
-
@media (max-width: 768px) {
|
| 726 |
-
.nav-menu {
|
| 727 |
-
gap: 1.5rem;
|
| 728 |
-
font-size: 0.85rem;
|
| 729 |
-
}
|
| 730 |
-
|
| 731 |
-
.site-title {
|
| 732 |
-
font-size: 1.2rem;
|
| 733 |
-
}
|
| 734 |
-
|
| 735 |
-
.s123-page-header {
|
| 736 |
-
font-size: 1.5rem;
|
| 737 |
-
}
|
| 738 |
-
|
| 739 |
-
.collection-container {
|
| 740 |
-
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
| 741 |
-
gap: 1rem;
|
| 742 |
-
}
|
| 743 |
-
|
| 744 |
-
footer .grid {
|
| 745 |
-
grid-template-columns: 1fr;
|
| 746 |
-
}
|
| 747 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
css/loader.css
DELETED
|
File without changes
|
css/product.css
DELETED
|
File without changes
|
css/style.css
DELETED
|
@@ -1,281 +0,0 @@
|
|
| 1 |
-
/* Reset and base styles */
|
| 2 |
-
* {
|
| 3 |
-
margin: 0;
|
| 4 |
-
padding: 0;
|
| 5 |
-
box-sizing: border-box;
|
| 6 |
-
}
|
| 7 |
-
|
| 8 |
-
body {
|
| 9 |
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 10 |
-
line-height: 1.6;
|
| 11 |
-
background-color: #f5f5f5;
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
/* Header styles */
|
| 15 |
-
.navbar {
|
| 16 |
-
display: flex;
|
| 17 |
-
justify-content: space-between;
|
| 18 |
-
align-items: center;
|
| 19 |
-
padding: 1rem 2rem;
|
| 20 |
-
background-color: #fff;
|
| 21 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
.logo h1 {
|
| 25 |
-
color: #00d4ff;
|
| 26 |
-
font-size: 2rem;
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
.search-bar {
|
| 30 |
-
flex: 1;
|
| 31 |
-
max-width: 600px;
|
| 32 |
-
margin: 0 2rem;
|
| 33 |
-
display: flex;
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
.search-bar input {
|
| 37 |
-
width: 100%;
|
| 38 |
-
padding: 0.8rem;
|
| 39 |
-
border: 2px solid #ddd;
|
| 40 |
-
border-radius: 4px 0 0 4px;
|
| 41 |
-
font-size: 1rem;
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
.search-bar button {
|
| 45 |
-
padding: 0.8rem 1.5rem;
|
| 46 |
-
background-color: #00d4ff;
|
| 47 |
-
border: none;
|
| 48 |
-
border-radius: 0 4px 4px 0;
|
| 49 |
-
color: white;
|
| 50 |
-
cursor: pointer;
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
.nav-links {
|
| 54 |
-
display: flex;
|
| 55 |
-
align-items: center;
|
| 56 |
-
gap: 1rem;
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
.nav-links a {
|
| 60 |
-
text-decoration: none;
|
| 61 |
-
color: #333;
|
| 62 |
-
padding: 0.5rem 1rem;
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
-
.login-btn, .signup-btn {
|
| 66 |
-
border: 2px solid #00d4ff;
|
| 67 |
-
border-radius: 4px;
|
| 68 |
-
transition: all 0.3s ease;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
.login-btn:hover, .signup-btn:hover {
|
| 72 |
-
background-color: #00d4ff;
|
| 73 |
-
color: white;
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
.cart-btn {
|
| 77 |
-
position: relative;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
.cart-count {
|
| 81 |
-
position: absolute;
|
| 82 |
-
top: -8px;
|
| 83 |
-
right: -8px;
|
| 84 |
-
background-color: #00d4ff;
|
| 85 |
-
color: white;
|
| 86 |
-
border-radius: 50%;
|
| 87 |
-
padding: 0.2rem 0.5rem;
|
| 88 |
-
font-size: 0.8rem;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
/* Categories menu */
|
| 92 |
-
.categories {
|
| 93 |
-
background-color: #333;
|
| 94 |
-
padding: 0.5rem 2rem;
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
.categories ul {
|
| 98 |
-
display: flex;
|
| 99 |
-
list-style: none;
|
| 100 |
-
gap: 2rem;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
.categories a {
|
| 104 |
-
color: white;
|
| 105 |
-
text-decoration: none;
|
| 106 |
-
font-size: 0.9rem;
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
/* Hero section */
|
| 110 |
-
.hero {
|
| 111 |
-
margin: 2rem 0;
|
| 112 |
-
padding: 0 2rem;
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
.hero-slider {
|
| 116 |
-
height: 400px;
|
| 117 |
-
background-color: #ddd;
|
| 118 |
-
border-radius: 8px;
|
| 119 |
-
margin-bottom: 2rem;
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
/* Product grid */
|
| 123 |
-
.product-grid {
|
| 124 |
-
display: grid;
|
| 125 |
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
| 126 |
-
gap: 2rem;
|
| 127 |
-
padding: 2rem;
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
.product-card {
|
| 131 |
-
background: white;
|
| 132 |
-
border-radius: 8px;
|
| 133 |
-
padding: 1rem;
|
| 134 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
| 135 |
-
transition: transform 0.3s ease;
|
| 136 |
-
}
|
| 137 |
-
|
| 138 |
-
.product-card:hover {
|
| 139 |
-
transform: translateY(-5px);
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
.product-card img {
|
| 143 |
-
width: 100%;
|
| 144 |
-
height: 200px;
|
| 145 |
-
object-fit: cover;
|
| 146 |
-
border-radius: 4px;
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
-
.product-card h3 {
|
| 150 |
-
margin: 1rem 0;
|
| 151 |
-
font-size: 1.1rem;
|
| 152 |
-
}
|
| 153 |
-
|
| 154 |
-
.product-card .price {
|
| 155 |
-
color: #00d4ff;
|
| 156 |
-
font-weight: bold;
|
| 157 |
-
font-size: 1.2rem;
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
/* AI Assistant */
|
| 161 |
-
.ai-chat-widget {
|
| 162 |
-
position: fixed;
|
| 163 |
-
bottom: 2rem;
|
| 164 |
-
right: 2rem;
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
#openAIChat {
|
| 168 |
-
background-color: #00d4ff;
|
| 169 |
-
color: white;
|
| 170 |
-
border: none;
|
| 171 |
-
padding: 1rem 2rem;
|
| 172 |
-
border-radius: 50px;
|
| 173 |
-
cursor: pointer;
|
| 174 |
-
display: flex;
|
| 175 |
-
align-items: center;
|
| 176 |
-
gap: 0.5rem;
|
| 177 |
-
box-shadow: 0 2px 10px rgba(240,71,74,0.3);
|
| 178 |
-
}
|
| 179 |
-
|
| 180 |
-
/* Modal styles */
|
| 181 |
-
.modal {
|
| 182 |
-
display: none;
|
| 183 |
-
position: fixed;
|
| 184 |
-
top: 0;
|
| 185 |
-
left: 0;
|
| 186 |
-
width: 100%;
|
| 187 |
-
height: 100%;
|
| 188 |
-
background-color: rgba(0,0,0,0.5);
|
| 189 |
-
}
|
| 190 |
-
|
| 191 |
-
.modal-content {
|
| 192 |
-
background-color: white;
|
| 193 |
-
width: 90%;
|
| 194 |
-
max-width: 500px;
|
| 195 |
-
margin: 2rem auto;
|
| 196 |
-
border-radius: 8px;
|
| 197 |
-
padding: 2rem;
|
| 198 |
-
}
|
| 199 |
-
|
| 200 |
-
.chat-container {
|
| 201 |
-
height: 500px;
|
| 202 |
-
display: flex;
|
| 203 |
-
flex-direction: column;
|
| 204 |
-
}
|
| 205 |
-
|
| 206 |
-
.chat-messages {
|
| 207 |
-
flex: 1;
|
| 208 |
-
overflow-y: auto;
|
| 209 |
-
padding: 1rem;
|
| 210 |
-
border: 1px solid #ddd;
|
| 211 |
-
border-radius: 4px;
|
| 212 |
-
margin-bottom: 1rem;
|
| 213 |
-
}
|
| 214 |
-
|
| 215 |
-
.chat-input {
|
| 216 |
-
display: flex;
|
| 217 |
-
gap: 1rem;
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
.chat-input input {
|
| 221 |
-
flex: 1;
|
| 222 |
-
padding: 0.8rem;
|
| 223 |
-
border: 2px solid #ddd;
|
| 224 |
-
border-radius: 4px;
|
| 225 |
-
}
|
| 226 |
-
|
| 227 |
-
.chat-input button {
|
| 228 |
-
padding: 0.8rem 1.5rem;
|
| 229 |
-
background-color: #00d4ff;
|
| 230 |
-
color: white;
|
| 231 |
-
border: none;
|
| 232 |
-
border-radius: 4px;
|
| 233 |
-
cursor: pointer;
|
| 234 |
-
}
|
| 235 |
-
|
| 236 |
-
/* Footer styles */
|
| 237 |
-
footer {
|
| 238 |
-
background-color: #333;
|
| 239 |
-
color: white;
|
| 240 |
-
padding: 3rem 2rem 1rem;
|
| 241 |
-
margin-top: 3rem;
|
| 242 |
-
}
|
| 243 |
-
|
| 244 |
-
.footer-content {
|
| 245 |
-
display: grid;
|
| 246 |
-
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 247 |
-
gap: 2rem;
|
| 248 |
-
margin-bottom: 2rem;
|
| 249 |
-
}
|
| 250 |
-
|
| 251 |
-
.footer-section h3 {
|
| 252 |
-
margin-bottom: 1rem;
|
| 253 |
-
}
|
| 254 |
-
|
| 255 |
-
.footer-section ul {
|
| 256 |
-
list-style: none;
|
| 257 |
-
}
|
| 258 |
-
|
| 259 |
-
.footer-section ul li {
|
| 260 |
-
margin-bottom: 0.5rem;
|
| 261 |
-
}
|
| 262 |
-
|
| 263 |
-
.footer-section a {
|
| 264 |
-
color: white;
|
| 265 |
-
text-decoration: none;
|
| 266 |
-
}
|
| 267 |
-
|
| 268 |
-
.social-links {
|
| 269 |
-
display: flex;
|
| 270 |
-
gap: 1rem;
|
| 271 |
-
}
|
| 272 |
-
|
| 273 |
-
.social-links a {
|
| 274 |
-
font-size: 1.5rem;
|
| 275 |
-
}
|
| 276 |
-
|
| 277 |
-
.footer-bottom {
|
| 278 |
-
text-align: center;
|
| 279 |
-
padding-top: 2rem;
|
| 280 |
-
border-top: 1px solid #555;
|
| 281 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
css/styles.css
DELETED
|
File without changes
|
dist/style.css
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
body{margin:0;font-family:Segoe UI,Arial,sans-serif;background:#f9fafb;color:#222}.container{max-width:1200px;margin:0 auto;padding:0 1.5rem}.header{background:#fff;border-bottom:1px solid #e5e7eb;box-shadow:0 2px 8px 0 rgba(0,0,0,.02)}.header-flex{justify-content:space-between;height:64px;position:relative}.header-flex,.logo{display:flex;align-items:center}.logo{font-weight:700;font-size:1.3rem;color:#222;text-decoration:none}#logo{width:50px;height:50px}.logo-icon{background:#6366f1;color:#fff;border-radius:8px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;margin-right:8px;font-size:1.1rem}.nav{display:flex;gap:2rem}.nav a{color:#444;text-decoration:none;font-weight:500;transition:color .2s}.nav a:hover{color:#6366f1}.auth-btns{display:flex;gap:1rem}.btn{display:inline-block;padding:.6em 1.5em;border-radius:6px;font-size:1rem;font-weight:600;text-decoration:none;border:none;cursor:pointer;transition:background .2s,color .2s}.btn-primary{background:#6366f1;color:#fff}.btn-primary:hover{background:#4f46e5}.btn-outline{background:#fff;color:#6366f1;border:2px solid #6366f1}.btn-outline:hover{background:#f1f5f9}.btn-ghost{background:none;color:#6366f1}.hero{background:#fff;padding:4rem 0 3rem}.hero-grid{display:grid;grid-template-columns:1fr 1fr;gap:3rem;align-items:center}.hero-content h1{font-size:2.5rem;font-weight:700;color:#222;margin-bottom:1rem}.hero-content .primary{color:#6366f1}.hero-content p{font-size:1.2rem;color:#555;margin-bottom:2rem}.hero-actions{display:flex;gap:1rem;margin-bottom:1.5rem}.hero-list{list-style:none;padding:0;color:#6366f1;font-size:1rem;display:flex;gap:1.5rem}.hero-image{position:relative}.hero-image img{width:100%;border-radius:18px;box-shadow:0 8px 32px 0 rgba(0,0,0,.08)}.live-badge{position:absolute;left:1rem;bottom:1rem;background:#fff;color:#222;border-radius:8px;padding:.5em 1em;font-size:.95rem;box-shadow:0 2px 8px 0 rgba(0,0,0,.08);border:1px solid #e5e7eb}.features{background:#f9fafb;padding:4rem 0}.features h2{text-align:center;font-size:2rem;font-weight:700;margin-bottom:.5rem}.features .subtitle{text-align:center;color:#555;margin-bottom:2.5rem}.features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:2rem}.feature-card{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(0,0,0,.04);padding:2rem 1.5rem;text-align:left;border:1px solid #f1f5f9}.feature-icon{font-size:2rem;margin-bottom:1rem;color:#6366f1}.feature-card h3{font-size:1.2rem;font-weight:600;margin-bottom:.5rem}.feature-card p{color:#555;margin-bottom:1rem}.feature-card ul{list-style:none;padding:0;color:#6366f1;font-size:1rem}.pricing{background:#f9fafb;padding:4rem 0 2rem}.pricing h2{text-align:center;font-size:2rem;font-weight:700;margin-bottom:.5rem}.pricing .subtitle{text-align:center;color:#555;margin-bottom:2.5rem}.pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:2rem}.pricing-card{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(0,0,0,.04);padding:2rem 1.5rem;text-align:center;border:1px solid #e5e7eb;position:relative}.pricing-card.popular{border:2px solid #6366f1}.pricing-card .badge{position:absolute;top:-1.2rem;left:50%;transform:translateX(-50%);background:#f59e42;color:#fff;padding:.3em 1em;border-radius:8px;font-size:.95rem;font-weight:600}.pricing-card h3{font-size:1.2rem;font-weight:600;margin-bottom:.5rem}.pricing-card .price{font-size:2rem;font-weight:700;margin-bottom:.5rem}.pricing-card .price span{font-size:1rem;color:#6366f1;margin-left:.2em}.pricing-card ul{list-style:none;padding:0;color:#6366f1;font-size:1rem;margin-bottom:1.5rem}.pricing-footer{text-align:center;margin-top:2.5rem}.pricing-footer p{color:#555;margin-bottom:1rem}.pricing-icons{display:flex;justify-content:center;gap:2rem;color:#6366f1;font-size:1.1rem}.footer{background:#222;color:#fff;padding:3rem 0 1.5rem}.footer-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:2rem;margin-bottom:2rem}.footer .logo{font-weight:700;font-size:1.2rem;margin-bottom:1rem}.footer .logo,.footer .logo-icon{display:flex;align-items:center;color:#fff}.footer .logo-icon{background:#6366f1;border-radius:8px;width:32px;height:32px;justify-content:center;margin-right:8px;font-size:1.1rem}.footer-desc{color:#bbb;margin-bottom:1rem}.footer-socials a{font-size:1.3rem;margin-right:1rem;color:#bbb;text-decoration:none}.footer-socials a:hover{color:#fff}.footer h4{font-size:1.1rem;font-weight:600;margin-bottom:1rem}.footer ul{list-style:none;padding:0;color:#bbb;font-size:1rem}.footer ul li{margin-bottom:.5rem}.footer ul a{color:#bbb;text-decoration:none;transition:color .2s}.footer ul a:hover{color:#fff}.footer-bottom{border-top:1px solid #444;padding-top:1.2rem;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;color:#bbb;font-size:.95rem}.footer-links a{color:#bbb;margin-left:1.5rem;text-decoration:none}.footer-links a:hover{color:#fff}.auth-section{background:#f3f4f6;min-height:80vh;display:flex;align-items:center;justify-content:center;padding:3rem 0}.auth-container{background:#fff;border-radius:18px;box-shadow:0 4px 32px 0 rgba(99,102,241,.08);padding:2.5rem 2rem 2rem;max-width:400px;width:100%;margin:0 auto}.auth-toggle{display:flex;justify-content:center;gap:1rem;margin-bottom:2rem}.toggle-btn{background:#f3f4f6;border:none;padding:.7em 1.5em;border-radius:6px 6px 0 0;font-size:1rem;font-weight:600;color:#6366f1;cursor:pointer;transition:background .2s,color .2s}.toggle-btn.active{background:#6366f1;color:#fff;box-shadow:0 2px 8px 0 rgba(99,102,241,.08)}.auth-form{display:flex;flex-direction:column;gap:1rem;margin-bottom:1.5rem;animation:fadeIn .3s}.auth-form h2{text-align:center;color:#6366f1;margin-bottom:.5rem}.auth-form label{font-weight:500;color:#222;margin-bottom:.2rem}.auth-form input{padding:.7em 1em;border-radius:6px;border:1.5px solid #e5e7eb;font-size:1rem;background:#f9fafb;transition:border .2s}.auth-form input:focus{border:1.5px solid #6366f1;outline:none}.auth-form button[type=submit]{margin-top:.5rem}.auth-switch{text-align:center;color:#555;font-size:.98rem}.auth-switch a{color:#6366f1;text-decoration:none;font-weight:600;margin-left:.3em}.auth-switch a:hover{text-decoration:underline}.auth-error{color:#e11d48;margin-top:.5rem;display:none}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.auth-form.hidden{display:none}.menu-toggle{display:flex!important;flex-direction:column;justify-content:center;align-items:center;width:44px;height:44px;position:absolute;top:16px;right:24px;z-index:1100;background:transparent;border:none;cursor:pointer}.menu-bar{width:28px;height:4px;background:#222!important;margin:4px 0;border-radius:2px;transition:all .3s;display:block}.nav.mobile-menu{display:none;flex-direction:column;position:absolute;top:64px;right:1.5rem;background:#fff;box-shadow:0 8px 32px 0 rgba(0,0,0,.1);border-radius:12px;padding:1.5rem 2rem;z-index:1001;min-width:200px;gap:1.2rem}.mobile-hide,.nav.mobile-menu.open{display:flex}.header-right{display:flex;align-items:center;gap:1rem;margin-left:auto}.dashboard-section{background:#f9fafb;min-height:100vh;padding:3rem 0 2rem}.dashboard-section h1{text-align:center;font-size:2.2rem;font-weight:700;margin-bottom:2.5rem;color:#222}.dashboard-grid{grid-template-columns:repeat(auto-fit,minmax(340px,1fr));max-width:1200px;margin:0 auto}.dashboard-feature{border-radius:16px;box-shadow:0 4px 24px 0 rgba(99,102,241,.07);padding:2rem 1.5rem 1.5rem;transition:box-shadow .2s,border .2s}.dashboard-feature:hover{box-shadow:0 8px 32px 0 rgba(99,102,241,.13);border:1.5px solid #6366f1}.dashboard-feature h2{font-size:1.15rem;margin-bottom:.7rem}.dashboard-feature ul{margin-bottom:1.2rem;font-size:1rem}.dashboard-feature ul li{margin-bottom:.4rem}.dashboard-feature .btn{align-self:flex-end;margin-top:auto}.live-tools{flex:2;min-width:320px}.next-class{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(99,102,241,.07);padding:1.2rem 2rem;border:1.5px solid #e5e7eb;display:flex;flex-direction:column;gap:.7rem;max-width:420px}.dashboard-layout{background:#f5f6fa}.dashboard-layout,.sidebar{display:flex;min-height:100vh}.sidebar{width:220px;background:#222;color:#fff;flex-direction:column;padding:2rem 1rem 1rem}.sidebar .logo{margin-bottom:2.5rem;display:flex;align-items:center;font-size:1.3rem;font-weight:700;color:#fff}.sidebar nav{display:flex;flex-direction:column;gap:1.2rem}.sidebar nav .nav-section{margin-bottom:1.5rem}.sidebar nav .nav-section-title{color:#888;font-size:.9rem;margin-bottom:.5rem;text-transform:uppercase;letter-spacing:1px}.sidebar nav a{color:#bbb;text-decoration:none;font-size:1.05rem;display:flex;align-items:center;gap:.7rem;padding:.6em 1em;border-radius:8px;transition:background .2s,color .2s}.sidebar nav a.active,.sidebar nav a:hover{background:#6366f1;color:#fff}.sidebar .sidebar-footer{margin-top:auto;font-size:.95rem;color:#bbb;text-align:center}.main-area{flex:1;display:flex;flex-direction:column;min-width:0}.topbar{background:#fff;box-shadow:0 2px 8px 0 rgba(99,102,241,.04);height:64px;display:flex;align-items:center;justify-content:space-between;padding:0 2rem;border-bottom:1.5px solid #e5e7eb}.topbar .dashboard-title{font-size:1.3rem;font-weight:600;color:#222}.topbar .topbar-actions{display:flex;align-items:center;gap:1.5rem}.topbar .notification{font-size:1.3rem;color:#6366f1;cursor:pointer;position:relative}.topbar .notification .dot{position:absolute;top:2px;right:2px;width:8px;height:8px;background:#f59e42;border-radius:50%}.topbar .profile{display:flex;align-items:center;gap:.7rem;cursor:pointer}.topbar .profile-img{width:36px;height:36px;border-radius:50%;background:#6366f1;color:#fff;display:flex;align-items:center;justify-content:center;font-size:1.1rem;font-weight:700}.dashboard-content{flex:1;padding:2rem 2.5rem;display:flex;flex-direction:column;gap:2rem}.dashboard-row{display:flex;gap:2rem;flex-wrap:wrap;margin-bottom:2rem}.live-tools,.next-class{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(99,102,241,.07);padding:1.2rem 2rem;border:1.5px solid #e5e7eb;display:flex;flex-direction:column;gap:.7rem;max-width:420px;flex:2;min-width:320px}.class-title{font-size:1.1rem;font-weight:600;color:#222}.class-meta{color:#6366f1;font-size:.98rem}.class-actions{margin-top:.7rem;display:flex;gap:1rem}.live-tools-list{display:flex;gap:1rem;margin-top:1.2rem;list-style:none;padding:0}.live-tools-list li{margin:0}.activity-feed{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(99,102,241,.07);padding:1.2rem 2rem;border:1.5px solid #e5e7eb;flex:1;min-width:260px}.activity-feed h3{color:#6366f1;font-size:1.05rem;font-weight:600;margin-bottom:1rem}.activity-feed ul{list-style:none;padding:0;margin:0}.activity-feed li{margin-bottom:.7rem;color:#444;font-size:.98rem}.overview-widgets{display:flex;gap:2rem;flex-wrap:wrap}.widget{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(99,102,241,.07);padding:1.2rem 2rem;min-width:200px;flex:1 1 200px;display:flex;flex-direction:column;gap:.5rem;border:1.5px solid #e5e7eb}.widget .widget-title{color:#6366f1;font-size:1.05rem;font-weight:600}.widget .widget-value{font-size:1.5rem;font-weight:700;color:#222}.widget .widget-action{margin-top:.5rem}.dashboard-sections{display:flex;gap:2rem;flex-wrap:wrap}.dashboard-section-card{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(99,102,241,.07);padding:1.2rem 1.5rem;border:1.5px solid #e5e7eb;flex:1 1 320px;min-width:320px;display:flex;flex-direction:column;gap:.7rem}.dashboard-section-card h3{color:#6366f1;font-size:1.1rem;font-weight:600;margin-bottom:.5rem}.dashboard-section-card ul{color:#444;font-size:.98rem;margin-bottom:.5rem;padding-left:1.2rem}.dashboard-section-card ul li{margin-bottom:.2rem;list-style:disc}.dashboard-section-card .btn{align-self:flex-end;margin-top:auto}@media (max-width:1100px){.dashboard-row,.dashboard-sections,.overview-widgets{flex-direction:column;gap:1rem}.activity-feed,.live-tools,.next-class{min-width:unset;max-width:unset}}@media (max-width:900px){.dashboard-layout{flex-direction:column}.sidebar{width:100%;flex-direction:row;gap:1.5rem;padding:1rem .5rem;min-height:unset}.main-area{padding:0}.dashboard-content{padding:1.2rem .5rem}}.student-actions{margin-bottom:2.5rem;display:flex;justify-content:center}.find-book{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(99,102,241,.07);padding:2rem 2.5rem;display:flex;flex-direction:column;align-items:center;gap:1.2rem;border:1.5px solid #e5e7eb;max-width:500px;width:100%}.find-book h2{color:#6366f1;font-size:1.2rem;font-weight:600;margin-bottom:.5rem}.find-teacher-form{display:flex;gap:1rem;width:100%}.find-teacher-form .input{flex:1;padding:.7em 1em;border-radius:8px;border:1.5px solid #e5e7eb;font-size:1rem}.book-class-btn{width:100%;margin-top:.5rem}.dashboard-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:2rem;margin-top:2.5rem}.dashboard-feature{background:#fff;border-radius:14px;box-shadow:0 2px 8px 0 rgba(99,102,241,.07);padding:2rem 1.5rem;border:1.5px solid #e5e7eb;display:flex;flex-direction:column;align-items:flex-start;gap:1rem}.dashboard-feature h2{color:#6366f1;font-size:1.1rem;font-weight:600;margin-bottom:.5rem}.dashboard-feature ul{color:#444;font-size:.98rem;margin-bottom:.5rem;padding-left:1.2rem}.dashboard-feature ul li{margin-bottom:.2rem;list-style:disc}@media (max-width:700px){.find-book{padding:1.2rem .7rem}.dashboard-grid{gap:1rem;margin-top:1.2rem}}.profile-save-btn{margin-top:1rem}.profile-saved-msg{display:none;color:#22c55e;margin-top:.7rem;font-weight:500}#logout-btn{display:none}.search-container{margin-bottom:2rem}.search-input{width:300px;max-width:100%}.search-input,.search-select{margin-right:1rem}.search-select-subject{width:180px;max-width:100%}.search-select-price{width:140px;max-width:100%}.search-button{margin-left:1rem}.view-profile-btn{margin-top:1rem}.auth-container-wrapper{max-width:400px;margin:3rem auto}.auth-card{padding:2rem;background:#fff;border-radius:12px;box-shadow:0 2px 8px 0 rgba(0,0,0,.04)}.auth-logo{justify-content:center}.auth-title{font-size:2rem;font-weight:700;margin-bottom:.5rem}.auth-subtitle{color:#555;margin-bottom:1.5rem}.auth-input{width:100%;margin-bottom:1rem}.auth-input-password{width:100%;margin-bottom:1.5rem}.auth-submit-btn{width:100%}.auth-switch-container{text-align:center;margin-top:1.5rem}.auth-switch-text{color:#555}.auth-switch-link{color:#6366f1;text-decoration:none;margin-left:.5rem}
|
|
|
|
|
|
eeshapay-logo.html
DELETED
|
@@ -1,54 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html>
|
| 3 |
-
<head>
|
| 4 |
-
<style>
|
| 5 |
-
.eeshpay-logo { width: 480px; transition: all 0.3s ease; }
|
| 6 |
-
.eeshpay-logo:hover {
|
| 7 |
-
transform: translateY(-3px);
|
| 8 |
-
filter: drop-shadow(0 6px 12px rgba(251, 191, 36, 0.3));
|
| 9 |
-
}
|
| 10 |
-
.arrow {
|
| 11 |
-
stroke-dasharray: 380;
|
| 12 |
-
stroke-dashoffset: 380;
|
| 13 |
-
animation: draw 5s ease-out forwards;
|
| 14 |
-
}
|
| 15 |
-
.coin {
|
| 16 |
-
animation: spin 2s linear infinite;
|
| 17 |
-
transform-origin: 50px 40px;
|
| 18 |
-
}
|
| 19 |
-
@keyframes draw { to { stroke-dashoffset: 0; } }
|
| 20 |
-
@keyframes spin { to { transform: rotate(360deg); } }
|
| 21 |
-
</style>
|
| 22 |
-
</head>
|
| 23 |
-
<body>
|
| 24 |
-
|
| 25 |
-
<svg class="eeshpay-logo" viewBox="0 0 480 90" xmlns="http://www.w3.org/2000/svg">
|
| 26 |
-
<defs>
|
| 27 |
-
<linearGradient id="g1" x1="0%" y1="0%" x2="100%" y2="0%">
|
| 28 |
-
<stop offset="0%" stop-color="#0F172A"/>
|
| 29 |
-
<stop offset="100%" stop-color="#1E293B"/>
|
| 30 |
-
</linearGradient>
|
| 31 |
-
<linearGradient id="gold" x1="0%" y1="0%" x2="100%" y2="0%">
|
| 32 |
-
<stop offset="0%" stop-color="#FBBF24"/>
|
| 33 |
-
<stop offset="100%" stop-color="#F59E0B"/>
|
| 34 |
-
</linearGradient>
|
| 35 |
-
</defs>
|
| 36 |
-
|
| 37 |
-
<!-- Spinning Coin Icon -->
|
| 38 |
-
<g class="coin">
|
| 39 |
-
<circle cx="40" cy="40" r="18" fill="none" stroke="url(#gold)" stroke-width="3"/>
|
| 40 |
-
<text x="32" y="48" font-size="16" fill="url(#gold)" font-weight="bold">₦</text>
|
| 41 |
-
</g>
|
| 42 |
-
|
| 43 |
-
<!-- Eeshpay Text -->
|
| 44 |
-
<text x="80" y="65" font-family="Arial, Helvetica, sans-serif" font-size="60" font-weight="950" fill="url(#g1)" letter-spacing="-0.5">
|
| 45 |
-
Cellex
|
| 46 |
-
</text>
|
| 47 |
-
|
| 48 |
-
<!-- Flowing Arrow -->
|
| 49 |
-
<path class="arrow" d="M 150 68 Q 260 78, 440 68 L 430 58"
|
| 50 |
-
stroke="url(#gold)" stroke-width="4" fill="none" stroke-linecap="round"/>
|
| 51 |
-
</svg>
|
| 52 |
-
|
| 53 |
-
</body>
|
| 54 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eslint.config.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
| 2 |
+
import nextTypescript from "eslint-config-next/typescript";
|
| 3 |
+
import { dirname } from "path";
|
| 4 |
+
import { fileURLToPath } from "url";
|
| 5 |
+
|
| 6 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 7 |
+
const __dirname = dirname(__filename);
|
| 8 |
+
|
| 9 |
+
const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, {
|
| 10 |
+
rules: {
|
| 11 |
+
// TypeScript rules
|
| 12 |
+
"@typescript-eslint/no-explicit-any": "off",
|
| 13 |
+
"@typescript-eslint/no-unused-vars": "off",
|
| 14 |
+
"@typescript-eslint/no-non-null-assertion": "off",
|
| 15 |
+
"@typescript-eslint/ban-ts-comment": "off",
|
| 16 |
+
"@typescript-eslint/prefer-as-const": "off",
|
| 17 |
+
"@typescript-eslint/no-unused-disable-directive": "off",
|
| 18 |
+
|
| 19 |
+
// React rules
|
| 20 |
+
"react-hooks/exhaustive-deps": "off",
|
| 21 |
+
"react-hooks/purity": "off",
|
| 22 |
+
"react/no-unescaped-entities": "off",
|
| 23 |
+
"react/display-name": "off",
|
| 24 |
+
"react/prop-types": "off",
|
| 25 |
+
"react-compiler/react-compiler": "off",
|
| 26 |
+
|
| 27 |
+
// Next.js rules
|
| 28 |
+
"@next/next/no-img-element": "off",
|
| 29 |
+
"@next/next/no-html-link-for-pages": "off",
|
| 30 |
+
|
| 31 |
+
// General JavaScript rules
|
| 32 |
+
"prefer-const": "off",
|
| 33 |
+
"no-unused-vars": "off",
|
| 34 |
+
"no-console": "off",
|
| 35 |
+
"no-debugger": "off",
|
| 36 |
+
"no-empty": "off",
|
| 37 |
+
"no-irregular-whitespace": "off",
|
| 38 |
+
"no-case-declarations": "off",
|
| 39 |
+
"no-fallthrough": "off",
|
| 40 |
+
"no-mixed-spaces-and-tabs": "off",
|
| 41 |
+
"no-redeclare": "off",
|
| 42 |
+
"no-undef": "off",
|
| 43 |
+
"no-unreachable": "off",
|
| 44 |
+
"no-useless-escape": "off",
|
| 45 |
+
},
|
| 46 |
+
}, {
|
| 47 |
+
ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "next-env.d.ts", "examples/**", "skills"]
|
| 48 |
+
}];
|
| 49 |
+
|
| 50 |
+
export default eslintConfig;
|
group-buy.html
DELETED
|
@@ -1,200 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Group Buy · Cellex</title>
|
| 7 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 8 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
-
<link rel="stylesheet" href="css/common.css">
|
| 10 |
-
<script src="js/config/edge-functions.js?v=2"></script>
|
| 11 |
-
<style>
|
| 12 |
-
body { background: #f8fafc; }
|
| 13 |
-
.gb-hero {
|
| 14 |
-
background: linear-gradient(135deg, #00d4ff 0%, #0891b2 100%);
|
| 15 |
-
color: #0f172a; padding: 3rem 1.5rem;
|
| 16 |
-
}
|
| 17 |
-
.gb-card {
|
| 18 |
-
background: #fff; border-radius: 16px;
|
| 19 |
-
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
|
| 20 |
-
padding: 1.5rem; margin: 1rem auto; max-width: 720px;
|
| 21 |
-
}
|
| 22 |
-
.gb-progress-track {
|
| 23 |
-
width: 100%; height: 12px; background: #cffafe;
|
| 24 |
-
border-radius: 999px; overflow: hidden;
|
| 25 |
-
}
|
| 26 |
-
.gb-progress-fill {
|
| 27 |
-
height: 100%; background: linear-gradient(90deg, #00d4ff, #0891b2);
|
| 28 |
-
transition: width 0.5s ease;
|
| 29 |
-
}
|
| 30 |
-
.gb-avatars { display: flex; }
|
| 31 |
-
.gb-avatar {
|
| 32 |
-
width: 36px; height: 36px; border-radius: 50%;
|
| 33 |
-
background: #cffafe; color: #0e7490;
|
| 34 |
-
display: flex; align-items: center; justify-content: center;
|
| 35 |
-
font-weight: 700; font-size: 0.85rem;
|
| 36 |
-
border: 2px solid #fff; margin-left: -8px;
|
| 37 |
-
}
|
| 38 |
-
.gb-avatar:first-child { margin-left: 0; }
|
| 39 |
-
.gb-avatar.empty { background: #e2e8f0; color: #94a3b8; }
|
| 40 |
-
</style>
|
| 41 |
-
</head>
|
| 42 |
-
<body>
|
| 43 |
-
<div id="nav-mount"></div>
|
| 44 |
-
<div id="nav-mount"></div>
|
| 45 |
-
<script src="js/load-navbar.js"></script>
|
| 46 |
-
|
| 47 |
-
<div class="gb-hero">
|
| 48 |
-
<div class="max-w-3xl mx-auto text-center">
|
| 49 |
-
<div class="inline-flex items-center gap-2 bg-ink-900 text-cyan-400 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wide">
|
| 50 |
-
<i class="fas fa-users"></i> Group Buy
|
| 51 |
-
</div>
|
| 52 |
-
<h1 class="text-3xl md:text-4xl font-extrabold mt-3" id="gb-headline">Loading…</h1>
|
| 53 |
-
<p class="text-ink-900/80 mt-2">Get 20% off when 3 friends join</p>
|
| 54 |
-
</div>
|
| 55 |
-
</div>
|
| 56 |
-
|
| 57 |
-
<div class="gb-card" id="gb-card">
|
| 58 |
-
<div class="text-center py-8 text-gray-500">
|
| 59 |
-
<i class="fas fa-spinner fa-spin text-2xl"></i>
|
| 60 |
-
<div class="mt-2">Loading group buy…</div>
|
| 61 |
-
</div>
|
| 62 |
-
</div>
|
| 63 |
-
|
| 64 |
-
<script>
|
| 65 |
-
(async () => {
|
| 66 |
-
const params = new URLSearchParams(location.search);
|
| 67 |
-
const gbId = params.get('id');
|
| 68 |
-
if (!gbId) {
|
| 69 |
-
document.getElementById('gb-card').innerHTML = '<div class="text-center py-8 text-red-500">No group buy ID provided.</div>';
|
| 70 |
-
return;
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
// Load group buy status
|
| 74 |
-
const r = await window.EdgeFunctions.groupBuy.status(gbId);
|
| 75 |
-
if (!r.success || !r.groupBuy) {
|
| 76 |
-
document.getElementById('gb-card').innerHTML = '<div class="text-center py-8 text-red-500">Group buy not found.</div>';
|
| 77 |
-
return;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
const gb = r.groupBuy;
|
| 81 |
-
const product = r.product;
|
| 82 |
-
const pct = Math.min(100, Math.round((gb.current_count / gb.target_count) * 100));
|
| 83 |
-
const discountPrice = product ? (Number(product.price) * (1 - Number(gb.discount_pct) / 100)).toFixed(2) : null;
|
| 84 |
-
const isCompleted = gb.status === 'completed';
|
| 85 |
-
const isExpired = gb.status === 'expired' || (gb.status === 'open' && new Date(gb.expires_at) < new Date());
|
| 86 |
-
|
| 87 |
-
document.getElementById('gb-headline').textContent =
|
| 88 |
-
isCompleted ? '🎉 Group complete!' :
|
| 89 |
-
isExpired ? 'Group buy expired' :
|
| 90 |
-
`${gb.target_count - gb.current_count} more friend${gb.target_count - gb.current_count > 1 ? 's' : ''} needed!`;
|
| 91 |
-
|
| 92 |
-
// Build member avatars
|
| 93 |
-
const memberAvatars = (r.members || []).map(m => `
|
| 94 |
-
<div class="gb-avatar">${(m.user_name || (m.user_id || '').slice(0, 2) || '?').charAt(0).toUpperCase()}</div>
|
| 95 |
-
`).join('');
|
| 96 |
-
const emptySlots = Math.max(0, gb.target_count - (r.members?.length || 0));
|
| 97 |
-
const emptyAvatars = Array.from({length: emptySlots}).map(() => `<div class="gb-avatar empty">?</div>`).join('');
|
| 98 |
-
|
| 99 |
-
document.getElementById('gb-card').innerHTML = `
|
| 100 |
-
${product ? `
|
| 101 |
-
<div class="flex gap-4 mb-4">
|
| 102 |
-
${product.image_url
|
| 103 |
-
? `<img src="${product.image_url}" class="w-24 h-24 rounded-xl object-cover">`
|
| 104 |
-
: `<div class="w-24 h-24 rounded-xl bg-gray-100 flex items-center justify-center text-3xl text-gray-400"><i class="fas fa-box"></i></div>`}
|
| 105 |
-
<div class="flex-1">
|
| 106 |
-
<a href="product.html?id=${product.id}" class="font-bold text-lg hover:text-cyan-600">${escapeHtml(product.name)}</a>
|
| 107 |
-
<div class="flex items-baseline gap-2 mt-1">
|
| 108 |
-
<span class="text-2xl font-extrabold text-cyan-600">$${discountPrice}</span>
|
| 109 |
-
<span class="text-gray-400 line-through">$${Number(product.price).toFixed(2)}</span>
|
| 110 |
-
<span class="bg-cyan-100 text-amber-800 text-xs font-bold px-2 py-0.5 rounded">-${gb.discount_pct}%</span>
|
| 111 |
-
</div>
|
| 112 |
-
<div class="text-xs text-gray-500 mt-1">Category: ${escapeHtml(product.category || 'General')}</div>
|
| 113 |
-
</div>
|
| 114 |
-
</div>
|
| 115 |
-
` : ''}
|
| 116 |
-
|
| 117 |
-
<div class="my-6">
|
| 118 |
-
<div class="flex justify-between text-sm font-medium text-gray-700 mb-2">
|
| 119 |
-
<span>${gb.current_count} / ${gb.target_count} joined</span>
|
| 120 |
-
<span>${pct}%</span>
|
| 121 |
-
</div>
|
| 122 |
-
<div class="gb-progress-track">
|
| 123 |
-
<div class="gb-progress-fill" style="width: ${pct}%"></div>
|
| 124 |
-
</div>
|
| 125 |
-
<div class="gb-avatars mt-3">${memberAvatars}${emptyAvatars}</div>
|
| 126 |
-
</div>
|
| 127 |
-
|
| 128 |
-
<div class="bg-cyan-50 border border-cyan-200 rounded-lg p-3 text-sm text-amber-900">
|
| 129 |
-
<div class="font-semibold mb-1">
|
| 130 |
-
${isCompleted ? '✅ Group complete — discount unlocked!' :
|
| 131 |
-
isExpired ? '⏰ This group buy has expired.' :
|
| 132 |
-
`⏳ Expires ${new Date(gb.expires_at).toLocaleString()}`}
|
| 133 |
-
</div>
|
| 134 |
-
${isCompleted ? 'You and your friends can now check out at the discounted price.' : ''}
|
| 135 |
-
</div>
|
| 136 |
-
|
| 137 |
-
<div class="flex gap-2 mt-4">
|
| 138 |
-
${isCompleted ? `
|
| 139 |
-
<a href="product.html?id=${product?.id || ''}&gb=${gb.id}" class="flex-1 bg-gradient-to-r from-cyan-400 to-cyan-700 text-ink-900 font-bold text-center py-3 rounded-lg hover:opacity-90">
|
| 140 |
-
<i class="fas fa-shopping-cart"></i> Buy at $${discountPrice}
|
| 141 |
-
</a>
|
| 142 |
-
` : isExpired ? `
|
| 143 |
-
<button disabled class="flex-1 bg-gray-200 text-gray-500 font-bold py-3 rounded-lg cursor-not-allowed">Expired</button>
|
| 144 |
-
` : `
|
| 145 |
-
<button onclick="joinGroup('${gb.id}')" class="flex-1 bg-gradient-to-r from-cyan-400 to-cyan-700 text-ink-900 font-bold py-3 rounded-lg hover:opacity-90">
|
| 146 |
-
<i class="fas fa-plus"></i> Join group buy
|
| 147 |
-
</button>
|
| 148 |
-
`}
|
| 149 |
-
<button onclick="shareGroup('${gb.id}', '${escapeHtml(product?.name || 'this product')}')" class="px-4 bg-green-500 text-white font-bold rounded-lg hover:bg-green-600">
|
| 150 |
-
<i class="fab fa-whatsapp text-lg"></i>
|
| 151 |
-
</button>
|
| 152 |
-
<button onclick="copyGroupLink('${gb.id}')" class="px-4 bg-gray-100 text-gray-700 font-bold rounded-lg hover:bg-gray-200">
|
| 153 |
-
<i class="fas fa-link"></i>
|
| 154 |
-
</button>
|
| 155 |
-
</div>
|
| 156 |
-
`;
|
| 157 |
-
})();
|
| 158 |
-
|
| 159 |
-
async function joinGroup(gbId) {
|
| 160 |
-
// Check login
|
| 161 |
-
const sess = await window.EdgeFunctions.auth.checkSession();
|
| 162 |
-
if (!sess.user) {
|
| 163 |
-
if (confirm('Please login to join the group buy. Go to login page?')) {
|
| 164 |
-
window.location.href = 'login.html?next=' + encodeURIComponent('/group-buy.html?id=' + gbId);
|
| 165 |
-
}
|
| 166 |
-
return;
|
| 167 |
-
}
|
| 168 |
-
const r = await window.EdgeFunctions.groupBuy.join(gbId);
|
| 169 |
-
if (r.success) {
|
| 170 |
-
// Reload to show updated state
|
| 171 |
-
window.location.reload();
|
| 172 |
-
} else {
|
| 173 |
-
alert('Failed to join: ' + (r.error || 'Unknown error'));
|
| 174 |
-
}
|
| 175 |
-
}
|
| 176 |
-
|
| 177 |
-
function shareGroup(gbId, productName) {
|
| 178 |
-
const url = `${window.location.origin}/group-buy.html?id=${gbId}`;
|
| 179 |
-
const text = `🛍️ Join my group buy for "${productName}" on Cellex! Get 20% off when 3 of us join. ${url}`;
|
| 180 |
-
window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank');
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
async function copyGroupLink(gbId) {
|
| 184 |
-
const url = `${window.location.origin}/group-buy.html?id=${gbId}`;
|
| 185 |
-
try {
|
| 186 |
-
await navigator.clipboard.writeText(url);
|
| 187 |
-
alert('Link copied! Share it with friends.');
|
| 188 |
-
} catch {
|
| 189 |
-
window.prompt('Copy this link:', url);
|
| 190 |
-
}
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
function escapeHtml(s) {
|
| 194 |
-
return String(s ?? '').replace(/[&<>"']/g, c => ({
|
| 195 |
-
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
| 196 |
-
}[c]));
|
| 197 |
-
}
|
| 198 |
-
</script>
|
| 199 |
-
</body>
|
| 200 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.html
DELETED
|
@@ -1,1448 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
-
<title>Cellex — One App. Every Deal.</title>
|
| 7 |
-
<meta name="description" content="Shop millions of products from trusted merchants and local farmers. Best prices, fast delivery, secure payments.">
|
| 8 |
-
|
| 9 |
-
<!-- Fonts & Icons -->
|
| 10 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 11 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 12 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 13 |
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Sora:wght@600;700;800&display=swap" rel="stylesheet">
|
| 14 |
-
|
| 15 |
-
<!-- Tailwind only - NO Supabase SDK in frontend -->
|
| 16 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 17 |
-
|
| 18 |
-
<script>
|
| 19 |
-
tailwind.config = {
|
| 20 |
-
theme: {
|
| 21 |
-
extend: {
|
| 22 |
-
fontFamily: {
|
| 23 |
-
sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
|
| 24 |
-
display: ['Sora', 'system-ui', 'sans-serif'],
|
| 25 |
-
},
|
| 26 |
-
colors: {
|
| 27 |
-
brand: {
|
| 28 |
-
50: '#ecfeff',
|
| 29 |
-
100: '#cffafe',
|
| 30 |
-
200: '#a5f3fc',
|
| 31 |
-
300: '#67e8f9',
|
| 32 |
-
400: '#22d3ee',
|
| 33 |
-
500: '#00d4ff',
|
| 34 |
-
600: '#0891b2',
|
| 35 |
-
700: '#0e7490',
|
| 36 |
-
800: '#155e75',
|
| 37 |
-
900: '#164e63',
|
| 38 |
-
},
|
| 39 |
-
ink: {
|
| 40 |
-
900: '#0f172a',
|
| 41 |
-
800: '#1e293b',
|
| 42 |
-
700: '#334155',
|
| 43 |
-
600: '#475569',
|
| 44 |
-
500: '#64748b',
|
| 45 |
-
400: '#94a3b8',
|
| 46 |
-
300: '#cbd5e1',
|
| 47 |
-
200: '#e2e8f0',
|
| 48 |
-
100: '#f1f5f9',
|
| 49 |
-
},
|
| 50 |
-
},
|
| 51 |
-
boxShadow: {
|
| 52 |
-
'glow': '0 4px 24px -4px rgba(0,212,255,0.35)',
|
| 53 |
-
'glow-lg': '0 0 32px rgba(0,212,255,0.25)',
|
| 54 |
-
'card': '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
|
| 55 |
-
'pop': '0 4px 16px -2px rgba(0,0,0,0.1)',
|
| 56 |
-
'neon': '0 0 20px rgba(0,212,255,0.3), 0 0 40px rgba(0,212,255,0.1)',
|
| 57 |
-
},
|
| 58 |
-
animation: {
|
| 59 |
-
'float': 'float 3s ease-in-out infinite',
|
| 60 |
-
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 61 |
-
'slide-up': 'slideUp 0.5s ease-out',
|
| 62 |
-
'fade-in': 'fadeIn 0.5s ease-out',
|
| 63 |
-
'shimmer': 'shimmer 2s linear infinite',
|
| 64 |
-
'glow-pulse': 'glowPulse 2s ease-in-out infinite',
|
| 65 |
-
'scan': 'scan 3s linear infinite',
|
| 66 |
-
'neon-flicker': 'neonFlicker 4s ease-in-out infinite',
|
| 67 |
-
},
|
| 68 |
-
keyframes: {
|
| 69 |
-
float: { '0%, 100%': { transform: 'translateY(0)' }, '50%': { transform: 'translateY(-4px)' } },
|
| 70 |
-
slideUp: { '0%': { opacity: '0', transform: 'translateY(20px)' }, '100%': { opacity: '1', transform: 'translateY(0)' } },
|
| 71 |
-
fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' } },
|
| 72 |
-
shimmer: { '0%': { backgroundPosition: '-200% 0' }, '100%': { backgroundPosition: '200% 0' } },
|
| 73 |
-
glowPulse: { '0%, 100%': { boxShadow: '0 0 20px rgba(0,212,255,0.2)' }, '50%': { boxShadow: '0 0 40px rgba(0,212,255,0.5)' } },
|
| 74 |
-
scan: { '0%': { transform: 'translateY(-100%)' }, '100%': { transform: 'translateY(100%)' } },
|
| 75 |
-
neonFlicker: { '0%, 100%': { opacity: '1' }, '45%': { opacity: '0.85' }, '50%': { opacity: '1' }, '55%': { opacity: '0.9' } },
|
| 76 |
-
}
|
| 77 |
-
}
|
| 78 |
-
}
|
| 79 |
-
}
|
| 80 |
-
</script>
|
| 81 |
-
|
| 82 |
-
<style>
|
| 83 |
-
* { -webkit-tap-highlight-color: transparent; margin: 0; padding: 0; box-sizing: border-box; }
|
| 84 |
-
html { scroll-behavior: smooth; }
|
| 85 |
-
html, body { font-family: 'Plus Jakarta Sans', sans-serif; }
|
| 86 |
-
body {
|
| 87 |
-
background: #ffffff;
|
| 88 |
-
color: #0f172a;
|
| 89 |
-
overflow-x: hidden;
|
| 90 |
-
}
|
| 91 |
-
.font-display { font-family: 'Sora', sans-serif; }
|
| 92 |
-
|
| 93 |
-
/* Hide scrollbar */
|
| 94 |
-
.no-scrollbar::-webkit-scrollbar { display: none; }
|
| 95 |
-
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
| 96 |
-
|
| 97 |
-
/* Snap scroll */
|
| 98 |
-
.snap-x-mandatory { scroll-snap-type: x mandatory; }
|
| 99 |
-
.snap-center { scroll-snap-align: center; }
|
| 100 |
-
|
| 101 |
-
/* Gradients — single cyan accent, futuristic */
|
| 102 |
-
.grad-flame { background: linear-gradient(135deg, #00d4ff 0%, #0891b2 100%); }
|
| 103 |
-
.grad-night { background: #0f172a; }
|
| 104 |
-
.grad-flash { background: linear-gradient(135deg, #00d4ff 0%, #0891b2 100%); }
|
| 105 |
-
.grad-mint { background: linear-gradient(135deg, #00d4ff 0%, #0891b2 100%); }
|
| 106 |
-
.grad-gold { background: linear-gradient(135deg, #00d4ff 0%, #0891b2 100%); }
|
| 107 |
-
|
| 108 |
-
/* Pulse for live countdown */
|
| 109 |
-
@keyframes pulse-dot {
|
| 110 |
-
0%, 100% { opacity: 1; transform: scale(1); }
|
| 111 |
-
50% { opacity: 0.6; transform: scale(0.9); }
|
| 112 |
-
}
|
| 113 |
-
.pulse-dot { animation: pulse-dot 1s ease-in-out infinite; }
|
| 114 |
-
|
| 115 |
-
/* Slide up animation */
|
| 116 |
-
@keyframes slideUp {
|
| 117 |
-
from { opacity: 0; transform: translateY(20px); }
|
| 118 |
-
to { opacity: 1; transform: translateY(0); }
|
| 119 |
-
}
|
| 120 |
-
.reveal { animation: slideUp 0.5s ease-out both; }
|
| 121 |
-
|
| 122 |
-
/* Toast */
|
| 123 |
-
@keyframes toastIn {
|
| 124 |
-
from { opacity: 0; transform: translate(-50%, 30px); }
|
| 125 |
-
to { opacity: 1; transform: translate(-50%, 0); }
|
| 126 |
-
}
|
| 127 |
-
.toast-anim { animation: toastIn 0.3s ease-out; }
|
| 128 |
-
|
| 129 |
-
/* Progress bar fill — cyan with glow */
|
| 130 |
-
.progress-fill {
|
| 131 |
-
background: linear-gradient(90deg, #00d4ff, #0891b2);
|
| 132 |
-
transition: width 0.6s ease-out;
|
| 133 |
-
box-shadow: 0 0 12px rgba(0,212,255,0.4);
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
/* Heart pop */
|
| 137 |
-
@keyframes heartPop {
|
| 138 |
-
0% { transform: scale(1); }
|
| 139 |
-
50% { transform: scale(1.4); }
|
| 140 |
-
100% { transform: scale(1); }
|
| 141 |
-
}
|
| 142 |
-
.heart-pop { animation: heartPop 0.4s ease-out; }
|
| 143 |
-
|
| 144 |
-
/* Card hover/active */
|
| 145 |
-
.lift { transition: transform 0.25s ease, box-shadow 0.25s ease; }
|
| 146 |
-
.lift:active { transform: scale(0.97); }
|
| 147 |
-
.product-card { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
|
| 148 |
-
.product-card:hover { transform: translateY(-4px); box-shadow: 0 20px 40px -12px rgba(0,0,0,0.12); }
|
| 149 |
-
.product-card:hover .product-image { transform: scale(1.08); }
|
| 150 |
-
.product-card .product-image { transition: transform 0.5s ease; }
|
| 151 |
-
|
| 152 |
-
/* Glow ring for active nav — cyan */
|
| 153 |
-
.nav-active { position: relative; }
|
| 154 |
-
.nav-active::before {
|
| 155 |
-
content: ''; position: absolute; inset: -8px -10px;
|
| 156 |
-
background: rgba(0,212,255,0.10);
|
| 157 |
-
border-radius: 16px; z-index: -1;
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
/* Star rating — cyan */
|
| 161 |
-
.stars {
|
| 162 |
-
background: linear-gradient(90deg, #00d4ff var(--p, 100%), #d1d5db var(--p, 100%));
|
| 163 |
-
-webkit-background-clip: text; background-clip: text; color: transparent;
|
| 164 |
-
}
|
| 165 |
-
|
| 166 |
-
/* Tab active — cyan with glow */
|
| 167 |
-
.tab-active { color: #0f172a; font-weight: 700; }
|
| 168 |
-
.tab-active::after {
|
| 169 |
-
content: ''; display: block; height: 3px;
|
| 170 |
-
background: linear-gradient(90deg, #00d4ff, #0891b2);
|
| 171 |
-
border-radius: 2px; margin-top: 6px;
|
| 172 |
-
box-shadow: 0 0 8px rgba(0,212,255,0.5);
|
| 173 |
-
}
|
| 174 |
-
|
| 175 |
-
/* Floating animation */
|
| 176 |
-
@keyframes float {
|
| 177 |
-
0%, 100% { transform: translateY(0); }
|
| 178 |
-
50% { transform: translateY(-4px); }
|
| 179 |
-
}
|
| 180 |
-
.float { animation: float 3s ease-in-out infinite; }
|
| 181 |
-
|
| 182 |
-
/* Skeleton shimmer */
|
| 183 |
-
@keyframes shimmerBg {
|
| 184 |
-
0% { background-position: -200% 0; }
|
| 185 |
-
100% { background-position: 200% 0; }
|
| 186 |
-
}
|
| 187 |
-
.skeleton {
|
| 188 |
-
background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
|
| 189 |
-
background-size: 200% 100%;
|
| 190 |
-
animation: shimmerBg 1.5s infinite;
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
/* Header shadow on scroll */
|
| 194 |
-
header.scrolled { box-shadow: 0 4px 20px rgba(0,0,0,0.08); }
|
| 195 |
-
|
| 196 |
-
/* Search focus — cyan glow */
|
| 197 |
-
.search-box:focus-within {
|
| 198 |
-
box-shadow: 0 0 0 2px rgba(0,212,255,0.2), 0 0 20px rgba(0,212,255,0.15);
|
| 199 |
-
border-color: #00d4ff !important;
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
-
/* Mobile bottom nav safe area */
|
| 203 |
-
.safe-bottom { padding-bottom: env(safe-area-inset-bottom, 0); }
|
| 204 |
-
|
| 205 |
-
/* Quick action */
|
| 206 |
-
.quick-action { opacity: 0; transform: translateY(10px); transition: all 0.3s ease; }
|
| 207 |
-
.product-card:hover .quick-action { opacity: 1; transform: translateY(0); }
|
| 208 |
-
|
| 209 |
-
/* Seller badges — unified cyan */
|
| 210 |
-
.seller-badge { background: linear-gradient(135deg, #00d4ff, #0891b2); }
|
| 211 |
-
.farmer-badge { background: linear-gradient(135deg, #00d4ff, #0891b2); }
|
| 212 |
-
|
| 213 |
-
/* Flash badge — cyan with pulse */
|
| 214 |
-
.flash-badge {
|
| 215 |
-
background: linear-gradient(135deg, #00d4ff, #0891b2);
|
| 216 |
-
animation: pulse 2s infinite;
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
/* Custom scrollbar */
|
| 220 |
-
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 221 |
-
::-webkit-scrollbar-track { background: #f1f5f9; }
|
| 222 |
-
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
|
| 223 |
-
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
| 224 |
-
|
| 225 |
-
/* Gradient text — cyan */
|
| 226 |
-
.gradient-text {
|
| 227 |
-
background: linear-gradient(135deg, #00d4ff 0%, #0891b2 100%);
|
| 228 |
-
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
| 229 |
-
}
|
| 230 |
-
|
| 231 |
-
/* Glass */
|
| 232 |
-
.glass { background: rgba(255,255,255,0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); }
|
| 233 |
-
|
| 234 |
-
/* Cart drawer transition */
|
| 235 |
-
.cart-drawer { transition: transform 0.3s ease; }
|
| 236 |
-
|
| 237 |
-
/* === FUTURISTIC ANIMATIONS === */
|
| 238 |
-
/* Neon border glow on focus/hover */
|
| 239 |
-
.neon-border { border: 1px solid rgba(0,212,255,0.3); transition: all 0.3s ease; }
|
| 240 |
-
.neon-border:hover { border-color: #00d4ff; box-shadow: 0 0 16px rgba(0,212,255,0.3), inset 0 0 8px rgba(0,212,255,0.05); }
|
| 241 |
-
|
| 242 |
-
/* Holographic shimmer for cards */
|
| 243 |
-
.holo-card { position: relative; overflow: hidden; }
|
| 244 |
-
.holo-card::before {
|
| 245 |
-
content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
| 246 |
-
background: linear-gradient(90deg, transparent, rgba(0,212,255,0.08), transparent);
|
| 247 |
-
transition: left 0.6s ease;
|
| 248 |
-
}
|
| 249 |
-
.holo-card:hover::before { left: 100%; }
|
| 250 |
-
|
| 251 |
-
/* Scanline effect for live elements */
|
| 252 |
-
.scanline { position: relative; overflow: hidden; }
|
| 253 |
-
.scanline::after {
|
| 254 |
-
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
|
| 255 |
-
background: linear-gradient(90deg, transparent, #00d4ff, transparent);
|
| 256 |
-
animation: scan 3s linear infinite;
|
| 257 |
-
}
|
| 258 |
-
@keyframes scan {
|
| 259 |
-
0% { top: 0; opacity: 0; }
|
| 260 |
-
10% { opacity: 1; }
|
| 261 |
-
90% { opacity: 1; }
|
| 262 |
-
100% { top: 100%; opacity: 0; }
|
| 263 |
-
}
|
| 264 |
-
|
| 265 |
-
/* Glow pulse for CTAs */
|
| 266 |
-
.glow-pulse { animation: glowPulse 2s ease-in-out infinite; }
|
| 267 |
-
@keyframes glowPulse {
|
| 268 |
-
0%, 100% { box-shadow: 0 0 20px rgba(0,212,255,0.2); }
|
| 269 |
-
50% { box-shadow: 0 0 40px rgba(0,212,255,0.5), 0 0 60px rgba(0,212,255,0.2); }
|
| 270 |
-
}
|
| 271 |
-
|
| 272 |
-
/* Neon text flicker */
|
| 273 |
-
.neon-text {
|
| 274 |
-
color: #00d4ff;
|
| 275 |
-
text-shadow: 0 0 10px rgba(0,212,255,0.5), 0 0 20px rgba(0,212,255,0.3);
|
| 276 |
-
}
|
| 277 |
-
|
| 278 |
-
/* Smooth reveal on scroll */
|
| 279 |
-
.reveal-up { opacity: 0; transform: translateY(20px); transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
|
| 280 |
-
.reveal-up.visible { opacity: 1; transform: translateY(0); }
|
| 281 |
-
|
| 282 |
-
/* Card hover with cyan lift */
|
| 283 |
-
.lift { transition: transform 0.25s ease, box-shadow 0.25s ease; }
|
| 284 |
-
.lift:hover {
|
| 285 |
-
transform: translateY(-2px);
|
| 286 |
-
box-shadow: 0 8px 24px rgba(0,212,255,0.15), 0 0 0 1px rgba(0,212,255,0.1);
|
| 287 |
-
}
|
| 288 |
-
.lift:active { transform: scale(0.97); }
|
| 289 |
-
</style>
|
| 290 |
-
</head>
|
| 291 |
-
|
| 292 |
-
<body class="min-h-screen">
|
| 293 |
-
<!-- Universal Navbar -->
|
| 294 |
-
<div id="nav-mount"></div>
|
| 295 |
-
<script src="js/load-navbar.js"></script>
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
<!-- Main Content -->
|
| 299 |
-
<main class="pb-20 max-w-7xl mx-auto px-3 sm:px-4 lg:px-6">
|
| 300 |
-
<!-- Stories Bar (Phase 3 — Instagram-style, 24h stories) -->
|
| 301 |
-
<section id="storiesBarSection" class="mt-3 hidden">
|
| 302 |
-
<div class="bg-white rounded-2xl border border-ink-100 shadow-card p-3 overflow-x-auto">
|
| 303 |
-
<div id="storiesBarContainer" class="flex gap-3" style="min-width:max-content;">
|
| 304 |
-
<!-- Stories loaded dynamically -->
|
| 305 |
-
</div>
|
| 306 |
-
</div>
|
| 307 |
-
</section>
|
| 308 |
-
|
| 309 |
-
<!-- Hero Carousel (Choice Day, Beauty Monday, BrandDay) -->
|
| 310 |
-
<div class="mt-3">
|
| 311 |
-
<div class="relative h-44 sm:h-52 lg:h-56 rounded-3xl overflow-hidden shadow-pop">
|
| 312 |
-
<div id="heroCarousel" class="flex h-full overflow-x-auto no-scrollbar snap-x-mandatory">
|
| 313 |
-
<!-- Slide 1: Choice Day -->
|
| 314 |
-
<div class="snap-center shrink-0 w-full grad-flame relative">
|
| 315 |
-
<div class="absolute inset-0 p-4 sm:p-5 flex flex-col justify-between">
|
| 316 |
-
<div>
|
| 317 |
-
<div class="inline-flex items-center gap-1.5 bg-white/25 backdrop-blur px-2.5 py-1 rounded-full">
|
| 318 |
-
<span class="w-1.5 h-1.5 bg-yellow-300 rounded-full pulse-dot"></span>
|
| 319 |
-
<span class="text-[10px] font-bold text-white tracking-wide">CHOICE DAY • 1ST EVERY MONTH</span>
|
| 320 |
-
</div>
|
| 321 |
-
<div class="mt-2 font-display font-extrabold text-white text-2xl sm:text-3xl leading-none">Up to <span class="text-yellow-300">70%</span> OFF</div>
|
| 322 |
-
<div class="text-white/90 text-xs mt-1.5">Hand-picked deals. Members only.</div>
|
| 323 |
-
</div>
|
| 324 |
-
<div class="flex items-end justify-between">
|
| 325 |
-
<div>
|
| 326 |
-
<div class="text-white/70 text-[10px] font-semibold uppercase tracking-wider">Ends in</div>
|
| 327 |
-
<div class="flex items-center gap-1 mt-1 font-display font-bold text-white text-lg">
|
| 328 |
-
<span class="bg-ink-900/40 backdrop-blur px-2 py-0.5 rounded-md" id="hours">12</span>:
|
| 329 |
-
<span class="bg-ink-900/40 backdrop-blur px-2 py-0.5 rounded-md" id="minutes">45</span>:
|
| 330 |
-
<span class="bg-ink-900/40 backdrop-blur px-2 py-0.5 rounded-md" id="seconds">30</span>
|
| 331 |
-
</div>
|
| 332 |
-
</div>
|
| 333 |
-
<a href="#flashDeals" class="bg-white text-brand-700 font-bold text-xs px-4 py-2 rounded-full shadow lift">Shop Now →</a>
|
| 334 |
-
</div>
|
| 335 |
-
</div>
|
| 336 |
-
<div class="absolute -right-6 -top-6 w-32 h-32 bg-yellow-300/30 rounded-full blur-2xl"></div>
|
| 337 |
-
<div class="absolute right-4 top-4 text-5xl float">🛍️</div>
|
| 338 |
-
</div>
|
| 339 |
-
|
| 340 |
-
<!-- Slide 2: Beauty Monday -->
|
| 341 |
-
<div class="snap-center shrink-0 w-full grad-night relative">
|
| 342 |
-
<div class="absolute inset-0 p-4 sm:p-5 flex flex-col justify-between">
|
| 343 |
-
<div>
|
| 344 |
-
<div class="inline-flex items-center gap-1.5 bg-pink-500/30 backdrop-blur px-2.5 py-1 rounded-full">
|
| 345 |
-
<span class="text-[10px] font-bold text-pink-200 tracking-wide">BEAUTY MONDAY</span>
|
| 346 |
-
</div>
|
| 347 |
-
<div class="mt-2 font-display font-extrabold text-yellow-300 text-2xl sm:text-3xl leading-none">40% OFF</div>
|
| 348 |
-
<div class="text-white/90 text-xs mt-1.5">Glow up. Limited time offer.</div>
|
| 349 |
-
</div>
|
| 350 |
-
<div class="flex items-end justify-between">
|
| 351 |
-
<div class="flex -space-x-2">
|
| 352 |
-
<div class="w-10 h-10 rounded-full bg-pink-200 border-2 border-white"></div>
|
| 353 |
-
<div class="w-10 h-10 rounded-full bg-rose-300 border-2 border-white"></div>
|
| 354 |
-
<div class="w-10 h-10 rounded-full bg-fuchsia-300 border-2 border-white"></div>
|
| 355 |
-
</div>
|
| 356 |
-
<a href="categories.html?category=beauty" class="bg-yellow-300 text-ink-900 font-bold text-xs px-4 py-2 rounded-full shadow lift">Get Glow →</a>
|
| 357 |
-
</div>
|
| 358 |
-
</div>
|
| 359 |
-
<div class="absolute right-4 top-4 text-5xl float" style="animation-delay: -1s">💄</div>
|
| 360 |
-
</div>
|
| 361 |
-
|
| 362 |
-
<!-- Slide 3: Brand Day -->
|
| 363 |
-
<div class="snap-center shrink-0 w-full bg-ink-800 relative">
|
| 364 |
-
<div class="absolute inset-0 p-4 sm:p-5 flex flex-col justify-between">
|
| 365 |
-
<div>
|
| 366 |
-
<div class="inline-flex items-center gap-1.5 bg-white/25 backdrop-blur px-2.5 py-1 rounded-full">
|
| 367 |
-
<span class="text-[10px] font-bold text-white tracking-wide">BRAND DAY • FEATURED</span>
|
| 368 |
-
</div>
|
| 369 |
-
<div class="mt-2 font-display font-extrabold text-white text-2xl sm:text-3xl leading-none">From <span class="text-yellow-300">₦5,000</span></div>
|
| 370 |
-
<div class="text-white/90 text-xs mt-1.5">Premium products. Official stores.</div>
|
| 371 |
-
</div>
|
| 372 |
-
<div class="flex items-end justify-between">
|
| 373 |
-
<div class="text-white text-[10px]">
|
| 374 |
-
<div class="opacity-70">Brand Gallery</div>
|
| 375 |
-
<div class="font-bold">Best prices</div>
|
| 376 |
-
</div>
|
| 377 |
-
<a href="categories.html" class="bg-white text-purple-700 font-bold text-xs px-4 py-2 rounded-full shadow lift">Visit Store →</a>
|
| 378 |
-
</div>
|
| 379 |
-
</div>
|
| 380 |
-
<div class="absolute right-4 top-4 text-5xl float" style="animation-delay: -2s">🔦</div>
|
| 381 |
-
</div>
|
| 382 |
-
</div>
|
| 383 |
-
<!-- Dots -->
|
| 384 |
-
<div class="absolute bottom-2 left-1/2 -translate-x-1/2 flex gap-1.5" id="heroDots">
|
| 385 |
-
<span class="w-1.5 h-1.5 rounded-full bg-white"></span>
|
| 386 |
-
<span class="w-1.5 h-1.5 rounded-full bg-white/40"></span>
|
| 387 |
-
<span class="w-1.5 h-1.5 rounded-full bg-white/40"></span>
|
| 388 |
-
</div>
|
| 389 |
-
</div>
|
| 390 |
-
</div>
|
| 391 |
-
|
| 392 |
-
<!-- Flash Sales (Jumia) -->
|
| 393 |
-
<section id="flashDeals" class="mt-5 rounded-3xl overflow-hidden shadow-pop">
|
| 394 |
-
<div class="grad-flash px-4 py-3 flex items-center justify-between">
|
| 395 |
-
<div class="flex items-center gap-2">
|
| 396 |
-
<span class="text-2xl">⚡</span>
|
| 397 |
-
<div>
|
| 398 |
-
<div class="font-display font-extrabold text-white text-base leading-none">FLASH SALES</div>
|
| 399 |
-
<div class="text-white/80 text-[10px] mt-0.5">Hurry up!</div>
|
| 400 |
-
</div>
|
| 401 |
-
</div>
|
| 402 |
-
<div class="flex items-center gap-1 font-display font-bold text-white text-sm">
|
| 403 |
-
<span class="bg-ink-900/40 backdrop-blur px-1.5 py-1 rounded" id="fs-h">00</span>:
|
| 404 |
-
<span class="bg-ink-900/40 backdrop-blur px-1.5 py-1 rounded" id="fs-m">02</span>:
|
| 405 |
-
<span class="bg-ink-900/40 backdrop-blur px-1.5 py-1 rounded pulse-dot" id="fs-s">15</span>
|
| 406 |
-
</div>
|
| 407 |
-
</div>
|
| 408 |
-
<div class="bg-white p-3">
|
| 409 |
-
<div id="flashDealsContainer" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 lg:gap-3">
|
| 410 |
-
<!-- Products loaded dynamically -->
|
| 411 |
-
</div>
|
| 412 |
-
</div>
|
| 413 |
-
</section>
|
| 414 |
-
|
| 415 |
-
<!-- Category Tiles (Jumia) -->
|
| 416 |
-
<section class="mt-5">
|
| 417 |
-
<div class="flex items-center justify-between mb-3">
|
| 418 |
-
<h2 class="font-display font-extrabold text-base">Shop by Category</h2>
|
| 419 |
-
<a href="categories.html" class="text-xs font-semibold text-cyan-600">See All →</a>
|
| 420 |
-
</div>
|
| 421 |
-
<div class="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-6 gap-2.5">
|
| 422 |
-
<a href="categories.html" class="aspect-square grad-flame rounded-2xl p-3 flex flex-col justify-between text-left lift relative overflow-hidden">
|
| 423 |
-
<span class="text-white font-display font-extrabold text-sm leading-tight">Awoof of<br>the Month</span>
|
| 424 |
-
<span class="text-3xl absolute -right-1 -bottom-1 opacity-80">🎁</span>
|
| 425 |
-
</a>
|
| 426 |
-
<a href="categories.html?category=phones" class="aspect-square bg-ink-900 rounded-2xl p-3 flex flex-col justify-between text-left lift relative overflow-hidden">
|
| 427 |
-
<div>
|
| 428 |
-
<div class="text-white font-display font-extrabold text-base">₦5,000</div>
|
| 429 |
-
<div class="text-white/70 text-[10px] font-semibold">Store</div>
|
| 430 |
-
</div>
|
| 431 |
-
<div class="text-yellow-400 font-display font-extrabold text-2xl">40%</div>
|
| 432 |
-
</a>
|
| 433 |
-
<a href="categories.html?category=baby" class="aspect-square bg-gradient-to-br from-sky-400 to-blue-500 rounded-2xl p-3 flex flex-col justify-between text-left lift relative overflow-hidden">
|
| 434 |
-
<span class="text-white font-display font-extrabold text-sm leading-tight">Kids, Baby<br>& More</span>
|
| 435 |
-
<span class="text-3xl absolute -right-1 -bottom-1 opacity-80">🧸</span>
|
| 436 |
-
</a>
|
| 437 |
-
<a href="#flashDeals" class="aspect-square bg-gradient-to-br from-cyan-400 to-cyan-600 rounded-2xl p-3 flex flex-col justify-between text-left lift relative overflow-hidden">
|
| 438 |
-
<div>
|
| 439 |
-
<div class="text-white font-display font-extrabold text-sm">Up to</div>
|
| 440 |
-
<div class="text-white font-display font-extrabold text-2xl">60% OFF</div>
|
| 441 |
-
</div>
|
| 442 |
-
<span class="text-2xl absolute right-2 bottom-1 opacity-80">🔥</span>
|
| 443 |
-
</a>
|
| 444 |
-
<a href="categories.html" class="aspect-square bg-ink-900 rounded-2xl p-3 flex flex-col justify-between text-left lift relative overflow-hidden">
|
| 445 |
-
<div>
|
| 446 |
-
<div class="text-white font-display font-extrabold text-sm">BUY 2</div>
|
| 447 |
-
<div class="text-white/80 text-[10px] font-semibold">PAY FOR 1</div>
|
| 448 |
-
</div>
|
| 449 |
-
<div class="text-yellow-400 font-display font-extrabold text-xl">50%</div>
|
| 450 |
-
</a>
|
| 451 |
-
<a href="#newArrivals" class="aspect-square bg-gradient-to-br from-emerald-400 to-teal-500 rounded-2xl p-3 flex flex-col justify-between text-left lift relative overflow-hidden">
|
| 452 |
-
<span class="text-white font-display font-extrabold text-sm leading-tight">New<br>Arrivals</span>
|
| 453 |
-
<span class="text-3xl absolute -right-1 -bottom-1 opacity-80">✨</span>
|
| 454 |
-
</a>
|
| 455 |
-
</div>
|
| 456 |
-
</section>
|
| 457 |
-
|
| 458 |
-
<!-- Trending Products -->
|
| 459 |
-
<section id="trending" class="mt-5">
|
| 460 |
-
<div class="flex items-center justify-between mb-3">
|
| 461 |
-
<div class="flex items-center gap-2">
|
| 462 |
-
<div class="w-10 h-10 grad-flame rounded-xl flex items-center justify-center">
|
| 463 |
-
<i class="fas fa-fire text-white text-lg"></i>
|
| 464 |
-
</div>
|
| 465 |
-
<div>
|
| 466 |
-
<h2 class="font-display font-extrabold text-base">Trending Now</h2>
|
| 467 |
-
<p class="text-ink-500 text-[11px] -mt-0.5">Most popular this week</p>
|
| 468 |
-
</div>
|
| 469 |
-
</div>
|
| 470 |
-
<a href="#" class="text-xs font-semibold text-cyan-600">See All →</a>
|
| 471 |
-
</div>
|
| 472 |
-
<div id="trendingProductsContainer" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 lg:gap-3">
|
| 473 |
-
<!-- Products loaded dynamically -->
|
| 474 |
-
</div>
|
| 475 |
-
</section>
|
| 476 |
-
|
| 477 |
-
<!-- Farm Fresh -->
|
| 478 |
-
<section class="mt-5 rounded-3xl overflow-hidden">
|
| 479 |
-
<div class="grad-mint px-4 py-3 flex items-center justify-between">
|
| 480 |
-
<div class="flex items-center gap-2">
|
| 481 |
-
<span class="text-2xl">🌱</span>
|
| 482 |
-
<div>
|
| 483 |
-
<div class="font-display font-extrabold text-white text-base leading-none">FARM FRESH</div>
|
| 484 |
-
<div class="text-white/80 text-[10px] mt-0.5">Direct from local farmers</div>
|
| 485 |
-
</div>
|
| 486 |
-
</div>
|
| 487 |
-
<a href="categories.html?category=farm" class="bg-white text-cyan-700 font-bold text-xs px-3 py-1.5 rounded-full lift">See All →</a>
|
| 488 |
-
</div>
|
| 489 |
-
<div class="bg-white p-3">
|
| 490 |
-
<div id="farmProductsContainer" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 lg:gap-3">
|
| 491 |
-
<!-- Farm products loaded dynamically -->
|
| 492 |
-
</div>
|
| 493 |
-
</div>
|
| 494 |
-
</section>
|
| 495 |
-
|
| 496 |
-
<!-- New Arrivals -->
|
| 497 |
-
<section id="newArrivals" class="mt-5">
|
| 498 |
-
<div class="flex items-center justify-between mb-3">
|
| 499 |
-
<div class="flex items-center gap-2">
|
| 500 |
-
<div class="w-10 h-10 bg-ink-800 rounded-xl flex items-center justify-center">
|
| 501 |
-
<i class="fas fa-sparkles text-white text-lg"></i>
|
| 502 |
-
</div>
|
| 503 |
-
<div>
|
| 504 |
-
<h2 class="font-display font-extrabold text-base">New Arrivals</h2>
|
| 505 |
-
<p class="text-ink-500 text-[11px] -mt-0.5">Fresh products just added</p>
|
| 506 |
-
</div>
|
| 507 |
-
</div>
|
| 508 |
-
<a href="categories.html?sort=newest" class="text-xs font-semibold text-cyan-600">See All →</a>
|
| 509 |
-
</div>
|
| 510 |
-
<div id="newArrivalsContainer" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 lg:gap-3">
|
| 511 |
-
<!-- New arrivals loaded dynamically -->
|
| 512 |
-
</div>
|
| 513 |
-
</section>
|
| 514 |
-
|
| 515 |
-
<!-- Following Activity Feed (only shown if user is logged in & follows sellers) -->
|
| 516 |
-
<section id="followingFeedSection" class="mt-5 hidden">
|
| 517 |
-
<div class="flex items-center justify-between mb-3">
|
| 518 |
-
<div class="flex items-center gap-2">
|
| 519 |
-
<div class="w-10 h-10 grad-flame rounded-xl flex items-center justify-center shadow-glow">
|
| 520 |
-
<i class="fas fa-stream text-white text-base"></i>
|
| 521 |
-
</div>
|
| 522 |
-
<div>
|
| 523 |
-
<h2 class="font-display font-extrabold text-base">From Sellers You Follow</h2>
|
| 524 |
-
<p class="text-ink-500 text-[11px] -mt-0.5">Latest updates from your favourite stores</p>
|
| 525 |
-
</div>
|
| 526 |
-
</div>
|
| 527 |
-
</div>
|
| 528 |
-
<div id="followingFeedContainer" class="bg-white rounded-2xl border border-ink-100 shadow-card p-3 lg:p-4">
|
| 529 |
-
<!-- Activity items loaded dynamically -->
|
| 530 |
-
</div>
|
| 531 |
-
</section>
|
| 532 |
-
|
| 533 |
-
<!-- Live Now (Phase 2 — Live shopping) -->
|
| 534 |
-
<section id="liveNowSection" class="mt-5 hidden">
|
| 535 |
-
<div class="flex items-center justify-between mb-3">
|
| 536 |
-
<div class="flex items-center gap-2">
|
| 537 |
-
<div class="w-10 h-10 bg-red-500 rounded-xl flex items-center justify-center shadow-glow">
|
| 538 |
-
<i class="fas fa-broadcast-tower text-white text-base"></i>
|
| 539 |
-
</div>
|
| 540 |
-
<div>
|
| 541 |
-
<h2 class="font-display font-extrabold text-base flex items-center gap-2">
|
| 542 |
-
Live Now
|
| 543 |
-
<span class="inline-block w-2 h-2 bg-red-500 rounded-full animate-pulse"></span>
|
| 544 |
-
</h2>
|
| 545 |
-
<p class="text-ink-500 text-[11px] -mt-0.5">Watch live demos & buy in real-time</p>
|
| 546 |
-
</div>
|
| 547 |
-
</div>
|
| 548 |
-
<a href="live.html" class="text-xs font-semibold text-cyan-600">All live →</a>
|
| 549 |
-
</div>
|
| 550 |
-
<div id="liveNowContainer" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 lg:gap-3">
|
| 551 |
-
<!-- Live sessions loaded dynamically -->
|
| 552 |
-
</div>
|
| 553 |
-
</section>
|
| 554 |
-
|
| 555 |
-
<!-- Meet the Top Sellers (Depop-style: 2x2 product grid + avatar + Shop button) -->
|
| 556 |
-
<section id="discoverSellers" class="mt-5">
|
| 557 |
-
<div class="mb-4">
|
| 558 |
-
<h2 class="text-xl font-extrabold text-gray-900">Meet the Top Sellers</h2>
|
| 559 |
-
<p class="text-gray-500 text-sm">Discover stores loved by shoppers</p>
|
| 560 |
-
</div>
|
| 561 |
-
<div id="discoverSellersContainer" class="space-y-4">
|
| 562 |
-
<!-- Seller cards loaded dynamically -->
|
| 563 |
-
</div>
|
| 564 |
-
</section>
|
| 565 |
-
|
| 566 |
-
<!-- Trending Now (Phase 3 — most viewed/shared/purchased in last 24h) -->
|
| 567 |
-
<section id="trendingSection" class="mt-5 hidden">
|
| 568 |
-
<div class="flex items-center justify-between mb-3">
|
| 569 |
-
<div class="flex items-center gap-2">
|
| 570 |
-
<div class="w-10 h-10 bg-gradient-to-br from-cyan-500 to-red-500 rounded-xl flex items-center justify-center shadow-glow">
|
| 571 |
-
<i class="fas fa-fire text-white text-base"></i>
|
| 572 |
-
</div>
|
| 573 |
-
<div>
|
| 574 |
-
<h2 class="font-display font-extrabold text-base">🔥 Trending Now</h2>
|
| 575 |
-
<p class="text-ink-500 text-[11px] -mt-0.5">Most viewed, shared & purchased in the last 24 hours</p>
|
| 576 |
-
</div>
|
| 577 |
-
</div>
|
| 578 |
-
<span class="text-[10px] text-ink-500" id="trendingRefreshedAt">—</span>
|
| 579 |
-
</div>
|
| 580 |
-
<div id="trendingContainer" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 lg:gap-3">
|
| 581 |
-
<!-- Trending products loaded dynamically -->
|
| 582 |
-
</div>
|
| 583 |
-
</section>
|
| 584 |
-
|
| 585 |
-
<!-- AI Recommendations (Phase 3 — only for logged-in users) -->
|
| 586 |
-
<section id="discoverSection" class="mt-5 hidden">
|
| 587 |
-
<div class="flex items-center justify-between mb-3">
|
| 588 |
-
<div class="flex items-center gap-2">
|
| 589 |
-
<div class="w-10 h-10 bg-gradient-to-br from-cyan-500 to-cyan-700 rounded-xl flex items-center justify-center shadow-glow">
|
| 590 |
-
<i class="fas fa-sparkles text-white text-base"></i>
|
| 591 |
-
</div>
|
| 592 |
-
<div>
|
| 593 |
-
<h2 class="font-display font-extrabold text-base">✨ For You</h2>
|
| 594 |
-
<p class="text-ink-500 text-[11px] -mt-0.5">AI picks based on your purchases, follows & activity</p>
|
| 595 |
-
</div>
|
| 596 |
-
</div>
|
| 597 |
-
</div>
|
| 598 |
-
<div id="discoverContainer" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2 lg:gap-3">
|
| 599 |
-
<!-- AI recommendations loaded dynamically -->
|
| 600 |
-
</div>
|
| 601 |
-
</section>
|
| 602 |
-
|
| 603 |
-
<!-- Product Videos CTA (Phase 3 — TikTok-style feed) -->
|
| 604 |
-
<section class="mt-5">
|
| 605 |
-
<a href="videos.html" class="block bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-700 rounded-3xl p-5 text-white relative overflow-hidden lift">
|
| 606 |
-
<div class="relative flex items-center gap-4">
|
| 607 |
-
<div class="w-14 h-14 bg-white/20 backdrop-blur rounded-2xl flex items-center justify-center">
|
| 608 |
-
<i class="fas fa-play-circle text-3xl"></i>
|
| 609 |
-
</div>
|
| 610 |
-
<div class="flex-1">
|
| 611 |
-
<div class="font-display font-extrabold text-lg">Shop what you see</div>
|
| 612 |
-
<div class="text-white/80 text-sm">Swipe through 15-second product videos from sellers</div>
|
| 613 |
-
</div>
|
| 614 |
-
<i class="fas fa-arrow-right text-2xl"></i>
|
| 615 |
-
</div>
|
| 616 |
-
</a>
|
| 617 |
-
</section>
|
| 618 |
-
|
| 619 |
-
<!-- Why Choose Cellex (Dark section) -->
|
| 620 |
-
<section class="mt-5 grad-night rounded-3xl p-5 lg:p-8">
|
| 621 |
-
<div class="text-center mb-5">
|
| 622 |
-
<h2 class="font-display text-xl lg:text-2xl font-bold text-white mb-1">Why Shop With Cellex?</h2>
|
| 623 |
-
<p class="text-gray-400 text-xs lg:text-sm">The safest, fastest marketplace in Nigeria</p>
|
| 624 |
-
</div>
|
| 625 |
-
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
| 626 |
-
<div class="bg-white/5 backdrop-blur-sm rounded-2xl p-4 border border-white/10 text-center">
|
| 627 |
-
<div class="w-12 h-12 mx-auto bg-trust-500/20 rounded-xl flex items-center justify-center mb-2">
|
| 628 |
-
<i class="fas fa-shield-alt text-trust-500 text-xl"></i>
|
| 629 |
-
</div>
|
| 630 |
-
<h3 class="font-semibold text-sm text-white mb-1">Buyer Protection</h3>
|
| 631 |
-
<p class="text-gray-400 text-[11px]">Full refund guarantee</p>
|
| 632 |
-
</div>
|
| 633 |
-
<div class="bg-white/5 backdrop-blur-sm rounded-2xl p-4 border border-white/10 text-center">
|
| 634 |
-
<div class="w-12 h-12 mx-auto bg-cyan-500/20 rounded-xl flex items-center justify-center mb-2">
|
| 635 |
-
<i class="fas fa-truck text-cyan-400 text-xl"></i>
|
| 636 |
-
</div>
|
| 637 |
-
<h3 class="font-semibold text-sm text-white mb-1">Fast Delivery</h3>
|
| 638 |
-
<p class="text-gray-400 text-[11px]">Nationwide shipping</p>
|
| 639 |
-
</div>
|
| 640 |
-
<div class="bg-white/5 backdrop-blur-sm rounded-2xl p-4 border border-white/10 text-center">
|
| 641 |
-
<div class="w-12 h-12 mx-auto bg-blue-500/20 rounded-xl flex items-center justify-center mb-2">
|
| 642 |
-
<i class="fas fa-undo text-blue-400 text-xl"></i>
|
| 643 |
-
</div>
|
| 644 |
-
<h3 class="font-semibold text-sm text-white mb-1">Easy Returns</h3>
|
| 645 |
-
<p class="text-gray-400 text-[11px]">7-day return policy</p>
|
| 646 |
-
</div>
|
| 647 |
-
<div class="bg-white/5 backdrop-blur-sm rounded-2xl p-4 border border-white/10 text-center">
|
| 648 |
-
<div class="w-12 h-12 mx-auto bg-purple-500/20 rounded-xl flex items-center justify-center mb-2">
|
| 649 |
-
<i class="fas fa-headset text-purple-400 text-xl"></i>
|
| 650 |
-
</div>
|
| 651 |
-
<h3 class="font-semibold text-sm text-white mb-1">24/7 Support</h3>
|
| 652 |
-
<p class="text-gray-400 text-[11px]">Always here to help</p>
|
| 653 |
-
</div>
|
| 654 |
-
</div>
|
| 655 |
-
</section>
|
| 656 |
-
|
| 657 |
-
<!-- Seller CTA -->
|
| 658 |
-
<section class="mt-5">
|
| 659 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
| 660 |
-
<div class="bg-white rounded-2xl p-4 lg:p-5 border border-ink-100 shadow-card lift">
|
| 661 |
-
<div class="flex items-start justify-between">
|
| 662 |
-
<div>
|
| 663 |
-
<h3 class="font-display text-base font-bold text-ink-900 mb-1">Start Selling Today</h3>
|
| 664 |
-
<p class="text-ink-500 text-xs mb-3">Join thousands of successful merchants</p>
|
| 665 |
-
<a href="signup.html?next=/seller/index.html" class="inline-flex items-center gap-1 grad-flame text-white px-4 py-2 rounded-lg text-xs font-medium lift">
|
| 666 |
-
Become a Merchant <i class="fas fa-arrow-right text-[10px]"></i>
|
| 667 |
-
</a>
|
| 668 |
-
</div>
|
| 669 |
-
<div class="text-brand-200"><i class="fas fa-store text-3xl"></i></div>
|
| 670 |
-
</div>
|
| 671 |
-
</div>
|
| 672 |
-
<div class="bg-white rounded-2xl p-4 lg:p-5 border border-ink-100 shadow-card lift">
|
| 673 |
-
<div class="flex items-start justify-between">
|
| 674 |
-
<div>
|
| 675 |
-
<div class="inline-flex items-center gap-1 bg-green-100 text-green-700 text-[10px] font-medium px-2 py-0.5 rounded-full mb-2">
|
| 676 |
-
<i class="fas fa-leaf text-[8px]"></i> SELLER CENTER
|
| 677 |
-
</div>
|
| 678 |
-
<h3 class="font-display text-base font-bold text-ink-900 mb-1">Open your dashboard</h3>
|
| 679 |
-
<p class="text-ink-500 text-xs mb-3">Manage products, orders, and followers</p>
|
| 680 |
-
<a href="seller/index.html" class="inline-flex items-center gap-1 bg-cyan-500 text-white px-4 py-2 rounded-lg text-xs font-medium hover:bg-emerald-600 lift">
|
| 681 |
-
Go to Dashboard <i class="fas fa-arrow-right text-[10px]"></i>
|
| 682 |
-
</a>
|
| 683 |
-
</div>
|
| 684 |
-
<div class="text-green-200"><i class="fas fa-tractor text-3xl"></i></div>
|
| 685 |
-
</div>
|
| 686 |
-
</div>
|
| 687 |
-
</div>
|
| 688 |
-
</section>
|
| 689 |
-
|
| 690 |
-
<!-- Sign-in CTA -->
|
| 691 |
-
<section class="mt-5">
|
| 692 |
-
<div class="grad-night rounded-3xl px-5 py-4 flex items-center justify-between relative overflow-hidden">
|
| 693 |
-
<div class="absolute -right-6 -top-6 w-32 h-32 bg-cyan-500/30 rounded-full blur-2xl"></div>
|
| 694 |
-
<div class="relative">
|
| 695 |
-
<div class="text-white font-display font-extrabold text-base leading-tight">Sign in for the<br>best experience</div>
|
| 696 |
-
<div class="text-white/70 text-[11px] mt-1">Member prices • Early access • Coins</div>
|
| 697 |
-
</div>
|
| 698 |
-
<a href="login.html" class="relative grad-flame text-white font-bold text-xs px-5 py-2.5 rounded-full shadow-glow lift">Sign in</a>
|
| 699 |
-
</div>
|
| 700 |
-
</section>
|
| 701 |
-
</main>
|
| 702 |
-
|
| 703 |
-
<!-- Footer -->
|
| 704 |
-
<footer class="bg-ink-900 text-white pt-10 pb-20 lg:pb-10 mt-5">
|
| 705 |
-
<div class="max-w-7xl mx-auto px-4">
|
| 706 |
-
<div class="grid grid-cols-2 lg:grid-cols-5 gap-8 mb-10">
|
| 707 |
-
<div class="col-span-2 lg:col-span-1">
|
| 708 |
-
<div class="flex items-center gap-2 mb-4">
|
| 709 |
-
<div class="w-10 h-10 grad-flame rounded-xl flex items-center justify-center shadow-glow">
|
| 710 |
-
<span class="font-display font-extrabold text-white text-lg">C</span>
|
| 711 |
-
</div>
|
| 712 |
-
<div class="flex flex-col">
|
| 713 |
-
<span class="text-xl font-extrabold gradient-text leading-tight font-display">Cellex</span>
|
| 714 |
-
<span class="text-[8px] text-gray-500 tracking-widest uppercase -mt-1">One App. Every Deal.</span>
|
| 715 |
-
</div>
|
| 716 |
-
</div>
|
| 717 |
-
<p class="text-gray-400 text-sm mb-4">Nigeria's #1 online marketplace connecting millions of buyers with trusted sellers.</p>
|
| 718 |
-
<div class="flex gap-3">
|
| 719 |
-
<a href="#" class="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center hover:bg-cyan-500 transition-colors"><i class="fab fa-facebook-f"></i></a>
|
| 720 |
-
<a href="#" class="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center hover:bg-cyan-500 transition-colors"><i class="fab fa-twitter"></i></a>
|
| 721 |
-
<a href="#" class="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center hover:bg-cyan-500 transition-colors"><i class="fab fa-instagram"></i></a>
|
| 722 |
-
<a href="#" class="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center hover:bg-cyan-500 transition-colors"><i class="fab fa-whatsapp"></i></a>
|
| 723 |
-
</div>
|
| 724 |
-
</div>
|
| 725 |
-
<div>
|
| 726 |
-
<h4 class="font-semibold text-cyan-400 mb-4">Quick Links</h4>
|
| 727 |
-
<ul class="space-y-2">
|
| 728 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">About Us</a></li>
|
| 729 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Contact Us</a></li>
|
| 730 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">FAQs</a></li>
|
| 731 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Blog</a></li>
|
| 732 |
-
</ul>
|
| 733 |
-
</div>
|
| 734 |
-
<div>
|
| 735 |
-
<h4 class="font-semibold text-cyan-400 mb-4">Customer Service</h4>
|
| 736 |
-
<ul class="space-y-2">
|
| 737 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Help Center</a></li>
|
| 738 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Returns</a></li>
|
| 739 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Shipping Info</a></li>
|
| 740 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Track Order</a></li>
|
| 741 |
-
</ul>
|
| 742 |
-
</div>
|
| 743 |
-
<div>
|
| 744 |
-
<h4 class="font-semibold text-cyan-400 mb-4">For Sellers</h4>
|
| 745 |
-
<ul class="space-y-2">
|
| 746 |
-
<li><a href="seller/index.html" class="text-gray-400 hover:text-white text-sm transition-colors">Seller Dashboard</a></li>
|
| 747 |
-
<li><a href="seller/go-live.html" class="text-gray-400 hover:text-white text-sm transition-colors">Go Live</a></li>
|
| 748 |
-
<li><a href="seller/videos.html" class="text-gray-400 hover:text-white text-sm transition-colors">Upload Product Video</a></li>
|
| 749 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Seller Guidelines</a></li>
|
| 750 |
-
</ul>
|
| 751 |
-
</div>
|
| 752 |
-
<div>
|
| 753 |
-
<h4 class="font-semibold text-cyan-400 mb-4">Shop Anywhere</h4>
|
| 754 |
-
<ul class="space-y-2">
|
| 755 |
-
<li><a href="whatsapp.html" class="text-gray-400 hover:text-white text-sm transition-colors"><i class="fab fa-whatsapp mr-1"></i> WhatsApp bot</a></li>
|
| 756 |
-
<li><a href="telegram.html" class="text-gray-400 hover:text-white text-sm transition-colors"><i class="fab fa-telegram mr-1"></i> Telegram channel</a></li>
|
| 757 |
-
<li><a href="link-account.html" class="text-gray-400 hover:text-white text-sm transition-colors"><i class="fas fa-link mr-1"></i> Link WhatsApp</a></li>
|
| 758 |
-
<li><a href="videos.html" class="text-gray-400 hover:text-white text-sm transition-colors"><i class="fas fa-play-circle mr-1"></i> Product videos</a></li>
|
| 759 |
-
</ul>
|
| 760 |
-
</div>
|
| 761 |
-
<div>
|
| 762 |
-
<h4 class="font-semibold text-cyan-400 mb-4">Legal</h4>
|
| 763 |
-
<ul class="space-y-2">
|
| 764 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Privacy Policy</a></li>
|
| 765 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Terms of Service</a></li>
|
| 766 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Cookie Policy</a></li>
|
| 767 |
-
<li><a href="#" class="text-gray-400 hover:text-white text-sm transition-colors">Disclaimer</a></li>
|
| 768 |
-
</ul>
|
| 769 |
-
</div>
|
| 770 |
-
</div>
|
| 771 |
-
<div class="border-t border-white/10 pt-6 mb-6">
|
| 772 |
-
<div class="flex flex-wrap items-center justify-center gap-4">
|
| 773 |
-
<span class="text-gray-400 text-sm">We accept:</span>
|
| 774 |
-
<div class="flex items-center gap-3">
|
| 775 |
-
<div class="bg-white/10 px-4 py-2 rounded-lg"><i class="fab fa-cc-visa text-2xl"></i></div>
|
| 776 |
-
<div class="bg-white/10 px-4 py-2 rounded-lg"><i class="fab fa-cc-mastercard text-2xl"></i></div>
|
| 777 |
-
<div class="bg-white/10 px-4 py-2 rounded-lg"><i class="fas fa-university text-2xl"></i></div>
|
| 778 |
-
<div class="bg-white/10 px-4 py-2 rounded-lg text-sm font-medium">Paystack</div>
|
| 779 |
-
</div>
|
| 780 |
-
</div>
|
| 781 |
-
</div>
|
| 782 |
-
<div class="text-center text-gray-400 text-sm">
|
| 783 |
-
<p>© 2025 Cellex. All rights reserved. Made with <i class="fas fa-heart text-red-500"></i> in Nigeria</p>
|
| 784 |
-
</div>
|
| 785 |
-
</div>
|
| 786 |
-
</footer>
|
| 787 |
-
|
| 788 |
-
<!-- Auth Modal -->
|
| 789 |
-
<div id="authModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden items-center justify-center p-4">
|
| 790 |
-
<div class="bg-white rounded-3xl max-w-md w-full p-8 text-center relative animate-slide-up">
|
| 791 |
-
<button id="closeAuthModal" class="absolute top-4 right-4 w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200 transition-colors">
|
| 792 |
-
<i class="fas fa-times text-gray-500"></i>
|
| 793 |
-
</button>
|
| 794 |
-
<div class="w-20 h-20 bg-brand-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
| 795 |
-
<i class="fas fa-user-lock text-cyan-500 text-3xl"></i>
|
| 796 |
-
</div>
|
| 797 |
-
<h3 class="font-display text-2xl font-bold text-ink-900 mb-2">Sign In Required</h3>
|
| 798 |
-
<p class="text-gray-500 mb-6">Please log in or create an account to add items to your cart</p>
|
| 799 |
-
<div class="flex gap-3">
|
| 800 |
-
<a href="login.html" class="flex-1 bg-ink-900 text-white py-3 rounded-full font-semibold hover:bg-ink-800 transition-colors">Login</a>
|
| 801 |
-
<a href="signup.html" class="flex-1 grad-flame text-white py-3 rounded-full font-semibold hover:shadow-glow transition-all">Sign Up</a>
|
| 802 |
-
</div>
|
| 803 |
-
</div>
|
| 804 |
-
</div>
|
| 805 |
-
|
| 806 |
-
<!-- Scripts -->
|
| 807 |
-
<script src="js/config/edge-functions.js?v=2"></script>
|
| 808 |
-
<script src="js/cart.js?v=10"></script>
|
| 809 |
-
<script src="js/ai-buyer-assistant.js?v=13"></script>
|
| 810 |
-
<script>
|
| 811 |
-
// ===== Global Variables =====
|
| 812 |
-
window.currentUser = null;
|
| 813 |
-
|
| 814 |
-
// ===== Initialize App =====
|
| 815 |
-
window.addEventListener('load', () => {
|
| 816 |
-
initializeApp();
|
| 817 |
-
});
|
| 818 |
-
|
| 819 |
-
async function initializeApp() {
|
| 820 |
-
await updateAuthUI();
|
| 821 |
-
await loadProducts();
|
| 822 |
-
setupEventListeners();
|
| 823 |
-
startCountdown();
|
| 824 |
-
startFlashCountdown();
|
| 825 |
-
setupHeroCarousel();
|
| 826 |
-
// Phase 1 — social commerce
|
| 827 |
-
// loadDiscoverSellers needs to know if user is logged in, so wait for auth
|
| 828 |
-
loadDiscoverSellers();
|
| 829 |
-
loadFollowingFeed();
|
| 830 |
-
// Phase 2 — live shopping
|
| 831 |
-
loadLiveNow();
|
| 832 |
-
// Phase 3 — content & discovery
|
| 833 |
-
loadStoriesBar();
|
| 834 |
-
loadTrending();
|
| 835 |
-
loadDiscoverForYou();
|
| 836 |
-
}
|
| 837 |
-
|
| 838 |
-
// ===== Auth Functions (via edge function) =====
|
| 839 |
-
// The new shared/navbar.html handles its own auth UI, so we just
|
| 840 |
-
// set window.currentUser for the home page's use.
|
| 841 |
-
async function updateAuthUI() {
|
| 842 |
-
try {
|
| 843 |
-
const result = await window.EdgeFunctions.auth.checkSession();
|
| 844 |
-
const user = result.user;
|
| 845 |
-
if (user) {
|
| 846 |
-
window.currentUser = user;
|
| 847 |
-
} else {
|
| 848 |
-
window.currentUser = null;
|
| 849 |
-
}
|
| 850 |
-
} catch (error) {
|
| 851 |
-
console.error('Auth error:', error);
|
| 852 |
-
}
|
| 853 |
-
}
|
| 854 |
-
|
| 855 |
-
// ===== Load Products (via Edge Function) =====
|
| 856 |
-
async function loadProducts() {
|
| 857 |
-
try {
|
| 858 |
-
const result = await window.EdgeFunctions.products.home();
|
| 859 |
-
|
| 860 |
-
if (!result.success) {
|
| 861 |
-
console.error('Error loading products:', result.error);
|
| 862 |
-
showPlaceholderProducts();
|
| 863 |
-
return;
|
| 864 |
-
}
|
| 865 |
-
|
| 866 |
-
renderProducts('flashDealsContainer', result.flashDeals || [], true);
|
| 867 |
-
renderProducts('trendingProductsContainer', result.trending || []);
|
| 868 |
-
renderProducts('farmProductsContainer', result.farmProducts || [], false, true);
|
| 869 |
-
renderProducts('newArrivalsContainer', result.newArrivals || []);
|
| 870 |
-
|
| 871 |
-
} catch (error) {
|
| 872 |
-
console.error('Error loading products:', error);
|
| 873 |
-
showPlaceholderProducts();
|
| 874 |
-
}
|
| 875 |
-
}
|
| 876 |
-
|
| 877 |
-
// ===== Render Products =====
|
| 878 |
-
function renderProducts(containerId, products, isFlashDeal = false, isFarmProduct = false) {
|
| 879 |
-
const container = document.getElementById(containerId);
|
| 880 |
-
if (!container) return;
|
| 881 |
-
|
| 882 |
-
if (!products || products.length === 0) {
|
| 883 |
-
container.innerHTML = getPlaceholderProducts(5);
|
| 884 |
-
return;
|
| 885 |
-
}
|
| 886 |
-
|
| 887 |
-
container.innerHTML = products.map(product => createProductCard(product, isFlashDeal, isFarmProduct)).join('');
|
| 888 |
-
}
|
| 889 |
-
|
| 890 |
-
// ===== Create Product Card =====
|
| 891 |
-
function createProductCard(product, isFlashDeal = false, isFarmProduct = false) {
|
| 892 |
-
const originalPrice = product.price || 0;
|
| 893 |
-
const discount = product.discount_percentage || 0;
|
| 894 |
-
const discountedPrice = discount > 0 ? originalPrice * (1 - discount / 100) : originalPrice;
|
| 895 |
-
const image = product.image_url || product.images?.[0] || 'https://via.placeholder.com/300x300?text=Product';
|
| 896 |
-
const name = product.name || product.title || 'Product Name';
|
| 897 |
-
const rating = product.rating || 4.5;
|
| 898 |
-
const reviews = product.reviews_count || Math.floor(Math.random() * 500);
|
| 899 |
-
|
| 900 |
-
const sellerBadge = isFarmProduct ?
|
| 901 |
-
'<span class="farmer-badge text-white text-xs px-2 py-0.5 rounded-full flex items-center gap-1"><i class="fas fa-leaf text-xs"></i> Farmer</span>' :
|
| 902 |
-
'<span class="seller-badge text-white text-xs px-2 py-0.5 rounded-full flex items-center gap-1"><i class="fas fa-store text-xs"></i> Merchant</span>';
|
| 903 |
-
|
| 904 |
-
const flashBadge = isFlashDeal ?
|
| 905 |
-
`<div class="flash-badge absolute top-2 left-2 text-white text-xs font-bold px-2 py-1 rounded-lg z-10">-${discount}%</div>` : '';
|
| 906 |
-
|
| 907 |
-
const stockBar = isFlashDeal ?
|
| 908 |
-
`<div class="mt-2">
|
| 909 |
-
<div class="h-1.5 bg-ink-100 rounded-full overflow-hidden">
|
| 910 |
-
<div class="h-full progress-fill" style="width: ${Math.random() * 60 + 20}%"></div>
|
| 911 |
-
</div>
|
| 912 |
-
<p class="text-[10px] text-urgency-600 font-medium mt-1"><i class="fas fa-fire"></i> Selling fast!</p>
|
| 913 |
-
</div>` : '';
|
| 914 |
-
|
| 915 |
-
return `
|
| 916 |
-
<div class="product-card bg-white rounded-2xl overflow-hidden shadow-card border border-ink-100 cursor-pointer relative group" onclick="viewProduct('${product.id}')">
|
| 917 |
-
${flashBadge}
|
| 918 |
-
<div class="relative overflow-hidden">
|
| 919 |
-
<img src="${image}" alt="${name}" class="product-image w-full h-40 sm:h-48 object-cover"
|
| 920 |
-
onerror="this.src='https://via.placeholder.com/300x300?text=Product'">
|
| 921 |
-
<button onclick="event.stopPropagation(); addToWishlist('${product.id}')"
|
| 922 |
-
class="absolute top-2 right-2 w-8 h-8 bg-white rounded-full flex items-center justify-center shadow-md opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-50">
|
| 923 |
-
<i class="far fa-heart text-gray-400 hover:text-red-500"></i>
|
| 924 |
-
</button>
|
| 925 |
-
<div class="quick-action absolute bottom-2 left-2 right-2">
|
| 926 |
-
<button onclick="event.stopPropagation(); addToCart('${product.id}')"
|
| 927 |
-
class="w-full bg-ink-900 text-white py-2 rounded-xl font-medium text-sm hover:bg-cyan-500 transition-colors">
|
| 928 |
-
<i class="fas fa-cart-plus mr-1"></i> Add to Cart
|
| 929 |
-
</button>
|
| 930 |
-
</div>
|
| 931 |
-
</div>
|
| 932 |
-
<div class="p-3">
|
| 933 |
-
<div class="flex items-center gap-2 mb-1">${sellerBadge}</div>
|
| 934 |
-
<h3 class="font-medium text-ink-900 text-sm line-clamp-2 mb-1">${name}</h3>
|
| 935 |
-
<div class="flex items-center gap-1 mb-2">
|
| 936 |
-
<div class="flex text-cyan-400 text-xs">
|
| 937 |
-
${'<i class="fas fa-star"></i>'.repeat(Math.floor(rating))}
|
| 938 |
-
${rating % 1 >= 0.5 ? '<i class="fas fa-star-half-alt"></i>' : ''}
|
| 939 |
-
</div>
|
| 940 |
-
<span class="text-gray-400 text-xs">(${reviews})</span>
|
| 941 |
-
</div>
|
| 942 |
-
<div class="flex items-center gap-2">
|
| 943 |
-
<span class="text-lg font-bold text-ink-900">₦${discountedPrice.toLocaleString()}</span>
|
| 944 |
-
${discount > 0 ? `<span class="text-sm text-gray-400 line-through">₦${originalPrice.toLocaleString()}</span>` : ''}
|
| 945 |
-
</div>
|
| 946 |
-
${stockBar}
|
| 947 |
-
</div>
|
| 948 |
-
</div>
|
| 949 |
-
`;
|
| 950 |
-
}
|
| 951 |
-
|
| 952 |
-
// ===== Placeholder Products =====
|
| 953 |
-
function getPlaceholderProducts(count) {
|
| 954 |
-
let html = '';
|
| 955 |
-
for (let i = 0; i < count; i++) {
|
| 956 |
-
html += `
|
| 957 |
-
<div class="bg-white rounded-2xl overflow-hidden shadow-card border border-ink-100">
|
| 958 |
-
<div class="skeleton w-full h-40 sm:h-48"></div>
|
| 959 |
-
<div class="p-3">
|
| 960 |
-
<div class="skeleton h-4 w-3/4 rounded mb-2"></div>
|
| 961 |
-
<div class="skeleton h-4 w-1/2 rounded mb-2"></div>
|
| 962 |
-
<div class="skeleton h-6 w-1/3 rounded"></div>
|
| 963 |
-
</div>
|
| 964 |
-
</div>
|
| 965 |
-
`;
|
| 966 |
-
}
|
| 967 |
-
return html;
|
| 968 |
-
}
|
| 969 |
-
|
| 970 |
-
function showPlaceholderProducts() {
|
| 971 |
-
['flashDealsContainer', 'trendingProductsContainer', 'farmProductsContainer', 'newArrivalsContainer'].forEach(id => {
|
| 972 |
-
const container = document.getElementById(id);
|
| 973 |
-
if (container) container.innerHTML = getPlaceholderProducts(5);
|
| 974 |
-
});
|
| 975 |
-
}
|
| 976 |
-
|
| 977 |
-
// ===== Event Listeners =====
|
| 978 |
-
function setupEventListeners() {
|
| 979 |
-
window.addEventListener('scroll', () => {
|
| 980 |
-
const header = document.getElementById('mainHeader');
|
| 981 |
-
if (window.scrollY > 50) header.classList.add('scrolled');
|
| 982 |
-
else header.classList.remove('scrolled');
|
| 983 |
-
});
|
| 984 |
-
|
| 985 |
-
const userAvatarBtn = document.getElementById('userAvatarBtn');
|
| 986 |
-
const userDropdown = document.getElementById('userDropdown');
|
| 987 |
-
if (userAvatarBtn && userDropdown) {
|
| 988 |
-
userAvatarBtn.addEventListener('click', (e) => {
|
| 989 |
-
e.stopPropagation();
|
| 990 |
-
userDropdown.classList.toggle('hidden');
|
| 991 |
-
});
|
| 992 |
-
document.addEventListener('click', () => userDropdown.classList.add('hidden'));
|
| 993 |
-
}
|
| 994 |
-
|
| 995 |
-
const logoutBtn = document.getElementById('logoutBtn');
|
| 996 |
-
if (logoutBtn) {
|
| 997 |
-
logoutBtn.addEventListener('click', async () => {
|
| 998 |
-
await window.EdgeFunctions.auth.logout();
|
| 999 |
-
window.location.reload();
|
| 1000 |
-
});
|
| 1001 |
-
}
|
| 1002 |
-
|
| 1003 |
-
const closeAuthModal = document.getElementById('closeAuthModal');
|
| 1004 |
-
const authModal = document.getElementById('authModal');
|
| 1005 |
-
if (closeAuthModal && authModal) {
|
| 1006 |
-
closeAuthModal.addEventListener('click', () => {
|
| 1007 |
-
authModal.classList.add('hidden');
|
| 1008 |
-
authModal.classList.remove('flex');
|
| 1009 |
-
});
|
| 1010 |
-
}
|
| 1011 |
-
}
|
| 1012 |
-
|
| 1013 |
-
// ===== Hero Countdown (hours, minutes, seconds) =====
|
| 1014 |
-
function startCountdown() {
|
| 1015 |
-
let hours = 12, minutes = 45, seconds = 30;
|
| 1016 |
-
setInterval(() => {
|
| 1017 |
-
seconds--;
|
| 1018 |
-
if (seconds < 0) {
|
| 1019 |
-
seconds = 59; minutes--;
|
| 1020 |
-
if (minutes < 0) {
|
| 1021 |
-
minutes = 59; hours--;
|
| 1022 |
-
if (hours < 0) hours = 23;
|
| 1023 |
-
}
|
| 1024 |
-
}
|
| 1025 |
-
document.getElementById('hours').textContent = String(hours).padStart(2, '0');
|
| 1026 |
-
document.getElementById('minutes').textContent = String(minutes).padStart(2, '0');
|
| 1027 |
-
document.getElementById('seconds').textContent = String(seconds).padStart(2, '0');
|
| 1028 |
-
}, 1000);
|
| 1029 |
-
}
|
| 1030 |
-
|
| 1031 |
-
// ===== Flash Sales Countdown =====
|
| 1032 |
-
function startFlashCountdown() {
|
| 1033 |
-
let flashSec = 2 * 60 + 15;
|
| 1034 |
-
function pad(n) { return String(n).padStart(2, '0'); }
|
| 1035 |
-
setInterval(() => {
|
| 1036 |
-
if (flashSec <= 0) flashSec = 2 * 60 + 15;
|
| 1037 |
-
flashSec--;
|
| 1038 |
-
const fsH = document.getElementById('fs-h');
|
| 1039 |
-
const fsM = document.getElementById('fs-m');
|
| 1040 |
-
const fsS = document.getElementById('fs-s');
|
| 1041 |
-
if (fsH) fsH.textContent = pad(Math.floor(flashSec / 3600));
|
| 1042 |
-
if (fsM) fsM.textContent = pad(Math.floor((flashSec % 3600) / 60));
|
| 1043 |
-
if (fsS) fsS.textContent = pad(flashSec % 60);
|
| 1044 |
-
}, 1000);
|
| 1045 |
-
}
|
| 1046 |
-
|
| 1047 |
-
// ===== Hero Carousel Auto-rotate =====
|
| 1048 |
-
function setupHeroCarousel() {
|
| 1049 |
-
const carousel = document.getElementById('heroCarousel');
|
| 1050 |
-
if (!carousel) return;
|
| 1051 |
-
const dots = document.querySelectorAll('#heroDots span');
|
| 1052 |
-
carousel.addEventListener('scroll', () => {
|
| 1053 |
-
const idx = Math.round(carousel.scrollLeft / carousel.offsetWidth);
|
| 1054 |
-
dots.forEach((d, i) => d.className = `w-1.5 h-1.5 rounded-full ${i === idx ? 'bg-white' : 'bg-white/40'}`);
|
| 1055 |
-
});
|
| 1056 |
-
let autoIdx = 0;
|
| 1057 |
-
setInterval(() => {
|
| 1058 |
-
autoIdx = (autoIdx + 1) % 3;
|
| 1059 |
-
carousel.scrollTo({ left: autoIdx * carousel.offsetWidth, behavior: 'smooth' });
|
| 1060 |
-
}, 4500);
|
| 1061 |
-
}
|
| 1062 |
-
|
| 1063 |
-
// ===== Product Actions =====
|
| 1064 |
-
function viewProduct(productId) {
|
| 1065 |
-
window.location.href = `product.html?id=${productId}`;
|
| 1066 |
-
}
|
| 1067 |
-
|
| 1068 |
-
async function addToCart(productId) {
|
| 1069 |
-
if (!window.currentUser) {
|
| 1070 |
-
const authModal = document.getElementById('authModal');
|
| 1071 |
-
authModal.classList.remove('hidden');
|
| 1072 |
-
authModal.classList.add('flex');
|
| 1073 |
-
return;
|
| 1074 |
-
}
|
| 1075 |
-
try {
|
| 1076 |
-
if (window.Cart) {
|
| 1077 |
-
const result = await window.Cart.addToCart(productId, 1);
|
| 1078 |
-
if (result.success) showNotification('Added to cart!', 'success');
|
| 1079 |
-
else if (result.requiresAuth) {
|
| 1080 |
-
const authModal = document.getElementById('authModal');
|
| 1081 |
-
authModal.classList.remove('hidden');
|
| 1082 |
-
authModal.classList.add('flex');
|
| 1083 |
-
} else alert(result.message || 'Failed to add to cart');
|
| 1084 |
-
} else {
|
| 1085 |
-
console.error('Cart module not loaded');
|
| 1086 |
-
alert('Cart service unavailable. Please refresh the page.');
|
| 1087 |
-
}
|
| 1088 |
-
} catch (error) {
|
| 1089 |
-
console.error('Error adding to cart:', error);
|
| 1090 |
-
alert('Failed to add to cart. Please try again.');
|
| 1091 |
-
}
|
| 1092 |
-
}
|
| 1093 |
-
|
| 1094 |
-
function showNotification(message, type = 'success') {
|
| 1095 |
-
const notification = document.createElement('div');
|
| 1096 |
-
notification.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-pop z-50 transition-all transform translate-y-0 ${
|
| 1097 |
-
type === 'success' ? 'bg-trust-500 text-white' : 'bg-red-500 text-white'
|
| 1098 |
-
}`;
|
| 1099 |
-
notification.innerHTML = `<div class="flex items-center gap-2"><i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'}"></i><span>${message}</span></div>`;
|
| 1100 |
-
document.body.appendChild(notification);
|
| 1101 |
-
setTimeout(() => {
|
| 1102 |
-
notification.classList.add('translate-y-20', 'opacity-0');
|
| 1103 |
-
setTimeout(() => notification.remove(), 300);
|
| 1104 |
-
}, 3000);
|
| 1105 |
-
}
|
| 1106 |
-
|
| 1107 |
-
async function addToWishlist(productId) {
|
| 1108 |
-
if (!window.currentUser) {
|
| 1109 |
-
const authModal = document.getElementById('authModal');
|
| 1110 |
-
authModal.classList.remove('hidden');
|
| 1111 |
-
authModal.classList.add('flex');
|
| 1112 |
-
return;
|
| 1113 |
-
}
|
| 1114 |
-
try { alert('Added to wishlist!'); } catch (error) { console.error('Error:', error); }
|
| 1115 |
-
}
|
| 1116 |
-
|
| 1117 |
-
// ===== Search =====
|
| 1118 |
-
function handleSearchKeypress(event) {
|
| 1119 |
-
if (event.key === 'Enter') performSearch();
|
| 1120 |
-
}
|
| 1121 |
-
|
| 1122 |
-
function performSearch() {
|
| 1123 |
-
const searchInput = document.getElementById('searchInput');
|
| 1124 |
-
const query = searchInput.value.trim();
|
| 1125 |
-
if (query) window.location.href = `search-result.html?q=${encodeURIComponent(query)}`;
|
| 1126 |
-
}
|
| 1127 |
-
|
| 1128 |
-
// ====================================================================
|
| 1129 |
-
// Phase 1 — Social commerce (Discover sellers, Following feed)
|
| 1130 |
-
// ====================================================================
|
| 1131 |
-
async function loadDiscoverSellers() {
|
| 1132 |
-
const container = document.getElementById('discoverSellersContainer');
|
| 1133 |
-
if (!container) return;
|
| 1134 |
-
try {
|
| 1135 |
-
// Always show ALL sellers (both logged in and logged out)
|
| 1136 |
-
const [sellersResp, productsResp, sess] = await Promise.all([
|
| 1137 |
-
window.EdgeFunctions.social.discover(10),
|
| 1138 |
-
window.EdgeFunctions.products.all(100),
|
| 1139 |
-
window.EdgeFunctions.auth.checkSession(),
|
| 1140 |
-
]);
|
| 1141 |
-
|
| 1142 |
-
// Set window.currentUser for other functions that need it
|
| 1143 |
-
window.currentUser = (sess.success && sess.user) ? sess.user : null;
|
| 1144 |
-
|
| 1145 |
-
if (!sellersResp.success || !sellersResp.sellers || sellersResp.sellers.length === 0) {
|
| 1146 |
-
container.innerHTML = `
|
| 1147 |
-
<div class="text-center py-8 text-gray-500 text-sm">
|
| 1148 |
-
<i class="fas fa-store text-3xl block mb-2 text-gray-300"></i>
|
| 1149 |
-
No sellers yet. <a href="seller/index.html" class="text-cyan-600 font-semibold">Open your store</a>
|
| 1150 |
-
</div>`;
|
| 1151 |
-
return;
|
| 1152 |
-
}
|
| 1153 |
-
|
| 1154 |
-
const allProducts = productsResp.success ? (productsResp.products || []) : [];
|
| 1155 |
-
|
| 1156 |
-
container.innerHTML = sellersResp.sellers.map(s => {
|
| 1157 |
-
const name = s.business_name || s.farm_name || 'Unnamed store';
|
| 1158 |
-
const initial = (name || '?').charAt(0).toUpperCase();
|
| 1159 |
-
const sellerId = s.id || s.seller_id;
|
| 1160 |
-
const sellerProducts = allProducts.filter(p => p.seller_id === sellerId).slice(0, 4);
|
| 1161 |
-
|
| 1162 |
-
let productGridHtml = '';
|
| 1163 |
-
if (sellerProducts.length > 0) {
|
| 1164 |
-
let gridItems = sellerProducts.map(p => {
|
| 1165 |
-
const img = p.image_url
|
| 1166 |
-
? `<img src="${p.image_url}" class="w-full aspect-square object-cover" alt="${escapeHtml(p.name)}">`
|
| 1167 |
-
: `<div class="w-full aspect-square bg-gray-100 flex items-center justify-center text-gray-400"><i class="fas fa-box text-2xl"></i></div>`;
|
| 1168 |
-
return `<a href="product.html?id=${p.id}" class="block rounded-lg overflow-hidden bg-gray-50">${img}</a>`;
|
| 1169 |
-
}).join('');
|
| 1170 |
-
const emptySlots = 4 - sellerProducts.length;
|
| 1171 |
-
for (let i = 0; i < emptySlots; i++) {
|
| 1172 |
-
gridItems += `<div class="w-full aspect-square bg-gray-50 rounded-lg flex items-center justify-center text-gray-300"><i class="fas fa-box text-2xl"></i></div>`;
|
| 1173 |
-
}
|
| 1174 |
-
productGridHtml = `<div class="grid grid-cols-2 gap-1.5 mb-3">${gridItems}</div>`;
|
| 1175 |
-
} else {
|
| 1176 |
-
productGridHtml = `<div class="grid grid-cols-2 gap-1.5 mb-3">
|
| 1177 |
-
<div class="w-full aspect-square bg-gray-50 rounded-lg"></div>
|
| 1178 |
-
<div class="w-full aspect-square bg-gray-50 rounded-lg"></div>
|
| 1179 |
-
<div class="w-full aspect-square bg-gray-50 rounded-lg"></div>
|
| 1180 |
-
<div class="w-full aspect-square bg-gray-50 rounded-lg"></div>
|
| 1181 |
-
</div>`;
|
| 1182 |
-
}
|
| 1183 |
-
|
| 1184 |
-
const avatar = s.profile_image
|
| 1185 |
-
? `<img src="${s.profile_image}" class="w-10 h-10 rounded-full object-cover" alt="">`
|
| 1186 |
-
: `<div class="w-10 h-10 rounded-full bg-gradient-to-br from-cyan-400 to-cyan-700 flex items-center justify-center text-white font-bold text-sm">${initial}</div>`;
|
| 1187 |
-
|
| 1188 |
-
return `
|
| 1189 |
-
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm p-3">
|
| 1190 |
-
${productGridHtml}
|
| 1191 |
-
<div class="flex items-center gap-3">
|
| 1192 |
-
<a href="seller-profile.html?id=${sellerId}" class="shrink-0">${avatar}</a>
|
| 1193 |
-
<div class="flex-1 min-w-0">
|
| 1194 |
-
<a href="seller-profile.html?id=${sellerId}" class="font-bold text-sm text-gray-900 hover:text-cyan-600 truncate block">${escapeHtml(name)}</a>
|
| 1195 |
-
<div class="text-xs text-gray-500">${s.followers || 0} followers · ${escapeHtml(s.business_category || s.seller_type || 'Seller')}</div>
|
| 1196 |
-
</div>
|
| 1197 |
-
<a href="seller-profile.html?id=${sellerId}" class="bg-gray-900 text-white text-sm font-semibold px-4 py-2 rounded-lg hover:bg-cyan-600 transition whitespace-nowrap">Shop</a>
|
| 1198 |
-
</div>
|
| 1199 |
-
</div>`;
|
| 1200 |
-
}).join('');
|
| 1201 |
-
} catch (e) {
|
| 1202 |
-
console.error('discoverSellers error:', e);
|
| 1203 |
-
}
|
| 1204 |
-
}
|
| 1205 |
-
|
| 1206 |
-
async function loadFollowingFeed() {
|
| 1207 |
-
const section = document.getElementById('followingFeedSection');
|
| 1208 |
-
const container = document.getElementById('followingFeedContainer');
|
| 1209 |
-
if (!section || !container) return;
|
| 1210 |
-
// Only show if user is logged in
|
| 1211 |
-
if (!window.currentUser) return;
|
| 1212 |
-
try {
|
| 1213 |
-
const resp = await window.EdgeFunctions.social.feed(15, 0);
|
| 1214 |
-
if (!resp.success || !resp.items || resp.items.length === 0) return;
|
| 1215 |
-
section.classList.remove('hidden');
|
| 1216 |
-
const iconMap = {
|
| 1217 |
-
new_product: { cls: 'bg-cyan-100 text-cyan-700', icon: 'fa-box' },
|
| 1218 |
-
price_drop: { cls: 'bg-pink-100 text-pink-700', icon: 'fa-tag' },
|
| 1219 |
-
restock: { cls: 'bg-green-100 text-green-700', icon: 'fa-truck' },
|
| 1220 |
-
sale_milestone: { cls: 'bg-blue-100 text-blue-700', icon: 'fa-trophy' },
|
| 1221 |
-
announcement: { cls: 'bg-cyan-100 text-cyan-700', icon: 'fa-bullhorn' },
|
| 1222 |
-
};
|
| 1223 |
-
container.innerHTML = resp.items.map(it => {
|
| 1224 |
-
const m = iconMap[it.activity_type] || iconMap.announcement;
|
| 1225 |
-
const sellerName = it.seller?.business_name || it.seller?.farm_name || 'Seller';
|
| 1226 |
-
const sellerImg = it.seller?.profile_image
|
| 1227 |
-
? `<img src="${it.seller.profile_image}" class="w-8 h-8 rounded-full object-cover">`
|
| 1228 |
-
: `<div class="w-8 h-8 rounded-full bg-gradient-to-br from-cyan-400 to-cyan-700 flex items-center justify-center font-bold text-ink-900 text-sm">${(sellerName || '?').charAt(0).toUpperCase()}</div>`;
|
| 1229 |
-
return `
|
| 1230 |
-
<a href="seller-profile.html?id=${it.seller_id}" class="flex items-start gap-3 p-2 hover:bg-ink-50 rounded-xl transition border-b border-ink-100 last:border-0">
|
| 1231 |
-
${sellerImg}
|
| 1232 |
-
<div class="flex-1 min-w-0">
|
| 1233 |
-
<div class="flex items-center gap-1.5 text-xs text-ink-500 mb-0.5">
|
| 1234 |
-
<span class="font-semibold text-ink-800">${escapeHtml(sellerName)}</span>
|
| 1235 |
-
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full ${m.cls} text-[9px] font-bold uppercase tracking-wide">
|
| 1236 |
-
<i class="fas ${m.icon} text-[8px]"></i> ${it.activity_type.replace(/_/g, ' ')}
|
| 1237 |
-
</span>
|
| 1238 |
-
<span class="ml-auto text-[10px]">${timeAgo(it.created_at)}</span>
|
| 1239 |
-
</div>
|
| 1240 |
-
<div class="text-sm font-medium text-ink-900">${escapeHtml(it.title)}</div>
|
| 1241 |
-
${it.body ? `<div class="text-xs text-ink-500 mt-0.5 line-clamp-2">${escapeHtml(it.body)}</div>` : ''}
|
| 1242 |
-
</div>
|
| 1243 |
-
${it.image_url ? `<img src="${it.image_url}" class="w-14 h-14 rounded-lg object-cover flex-shrink-0">` : ''}
|
| 1244 |
-
</a>`;
|
| 1245 |
-
}).join('');
|
| 1246 |
-
} catch (e) {
|
| 1247 |
-
console.error('followingFeed error:', e);
|
| 1248 |
-
}
|
| 1249 |
-
}
|
| 1250 |
-
|
| 1251 |
-
function escapeHtml(s) {
|
| 1252 |
-
return String(s ?? '').replace(/[&<>"']/g, c => ({
|
| 1253 |
-
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
| 1254 |
-
}[c]));
|
| 1255 |
-
}
|
| 1256 |
-
|
| 1257 |
-
function timeAgo(iso) {
|
| 1258 |
-
if (!iso) return '';
|
| 1259 |
-
const d = new Date(iso);
|
| 1260 |
-
const diff = (Date.now() - d.getTime()) / 1000;
|
| 1261 |
-
if (diff < 60) return 'just now';
|
| 1262 |
-
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
|
| 1263 |
-
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
|
| 1264 |
-
if (diff < 604800) return Math.floor(diff / 86400) + 'd ago';
|
| 1265 |
-
return d.toLocaleDateString();
|
| 1266 |
-
}
|
| 1267 |
-
|
| 1268 |
-
// ====================================================================
|
| 1269 |
-
// Phase 2 — Live shopping "Live Now" carousel
|
| 1270 |
-
// ====================================================================
|
| 1271 |
-
async function loadLiveNow() {
|
| 1272 |
-
const section = document.getElementById('liveNowSection');
|
| 1273 |
-
const container = document.getElementById('liveNowContainer');
|
| 1274 |
-
if (!section || !container) return;
|
| 1275 |
-
try {
|
| 1276 |
-
const resp = await window.EdgeFunctions.live.list('live');
|
| 1277 |
-
if (!resp.success || !resp.sessions || resp.sessions.length === 0) return;
|
| 1278 |
-
section.classList.remove('hidden');
|
| 1279 |
-
container.innerHTML = resp.sessions.slice(0, 6).map(s => {
|
| 1280 |
-
const sellerName = s.seller?.business_name || 'Seller';
|
| 1281 |
-
const sellerImg = s.seller?.profile_image;
|
| 1282 |
-
return `
|
| 1283 |
-
<a href="live-watch.html?id=${s.id}" class="bg-white rounded-2xl border border-ink-100 shadow-card overflow-hidden lift block hover:border-red-400 transition">
|
| 1284 |
-
<div class="aspect-video bg-gradient-to-br from-red-500 to-pink-600 flex items-center justify-center text-white relative">
|
| 1285 |
-
<i class="fas fa-broadcast-tower text-3xl"></i>
|
| 1286 |
-
<span class="absolute top-2 left-2 bg-red-600 text-white text-[10px] font-bold px-2 py-0.5 rounded-full uppercase">● Live</span>
|
| 1287 |
-
<span class="absolute top-2 right-2 bg-black/40 text-white text-[10px] px-2 py-0.5 rounded-full">👁 ${s.viewer_count || 0}</span>
|
| 1288 |
-
</div>
|
| 1289 |
-
<div class="p-3">
|
| 1290 |
-
<div class="font-bold text-sm line-clamp-1">${escapeHtml(s.title)}</div>
|
| 1291 |
-
<div class="flex items-center gap-1.5 mt-1.5 text-xs text-ink-500">
|
| 1292 |
-
${sellerImg
|
| 1293 |
-
? `<img src="${sellerImg}" class="w-4 h-4 rounded-full">`
|
| 1294 |
-
: `<div class="w-4 h-4 rounded-full bg-gradient-to-br from-cyan-400 to-cyan-700 flex items-center justify-center text-[8px] font-bold text-ink-900">${(sellerName||'?').charAt(0)}</div>`}
|
| 1295 |
-
<span class="truncate">${escapeHtml(sellerName)}</span>
|
| 1296 |
-
</div>
|
| 1297 |
-
</div>
|
| 1298 |
-
</a>`;
|
| 1299 |
-
}).join('');
|
| 1300 |
-
} catch (e) {
|
| 1301 |
-
console.error('liveNow error:', e);
|
| 1302 |
-
}
|
| 1303 |
-
}
|
| 1304 |
-
|
| 1305 |
-
// ====================================================================
|
| 1306 |
-
// Phase 3 — Stories bar, Trending, AI Discover
|
| 1307 |
-
// ====================================================================
|
| 1308 |
-
async function loadStoriesBar() {
|
| 1309 |
-
const section = document.getElementById('storiesBarSection');
|
| 1310 |
-
const container = document.getElementById('storiesBarContainer');
|
| 1311 |
-
if (!section || !container) return;
|
| 1312 |
-
try {
|
| 1313 |
-
const resp = await window.EdgeFunctions.stories.activeBar();
|
| 1314 |
-
if (!resp.success || !resp.stories || resp.stories.length === 0) return;
|
| 1315 |
-
|
| 1316 |
-
// If logged in, filter to only show stories from followed sellers
|
| 1317 |
-
// If not logged in, show ALL sellers' stories
|
| 1318 |
-
let storiesToShow = resp.stories;
|
| 1319 |
-
if (window.currentUser) {
|
| 1320 |
-
// Fetch followed seller IDs
|
| 1321 |
-
const followingResp = await window.EdgeFunctions.social.following();
|
| 1322 |
-
if (followingResp.success && followingResp.sellers) {
|
| 1323 |
-
const followedIds = new Set(followingResp.sellers.map(s => s.id));
|
| 1324 |
-
storiesToShow = resp.stories.filter(s => followedIds.has(s.seller_id));
|
| 1325 |
-
}
|
| 1326 |
-
// If user follows no sellers with stories, don't show the section
|
| 1327 |
-
if (storiesToShow.length === 0) return;
|
| 1328 |
-
}
|
| 1329 |
-
|
| 1330 |
-
section.classList.remove('hidden');
|
| 1331 |
-
container.innerHTML = storiesToShow.slice(0, 12).map(s => {
|
| 1332 |
-
const name = s.business_name || 'Seller';
|
| 1333 |
-
const initial = (name || '?').charAt(0).toUpperCase();
|
| 1334 |
-
const ringClass = s.has_unseen ? 'ring-cyan-400' : 'ring-gray-200';
|
| 1335 |
-
const img = s.profile_image
|
| 1336 |
-
? `<img src="${s.profile_image}" class="w-16 h-16 rounded-full object-cover">`
|
| 1337 |
-
: `<div class="w-16 h-16 rounded-full bg-gradient-to-br from-cyan-400 to-cyan-700 flex items-center justify-center font-bold text-2xl text-white">${initial}</div>`;
|
| 1338 |
-
return `
|
| 1339 |
-
<button onclick="openStoryViewer(${JSON.stringify(s.stories).replace(/"/g,'"')}, 0)" class="flex flex-col items-center gap-1 flex-shrink-0" style="width:72px;">
|
| 1340 |
-
<div class="p-0.5 rounded-full ring-2 ${ringClass}">${img}</div>
|
| 1341 |
-
<div class="text-[10px] text-gray-700 truncate w-full text-center">${escapeHtml(name.slice(0, 10))}</div>
|
| 1342 |
-
</button>`;
|
| 1343 |
-
}).join('');
|
| 1344 |
-
} catch (e) {
|
| 1345 |
-
console.error('storiesBar error:', e);
|
| 1346 |
-
}
|
| 1347 |
-
}
|
| 1348 |
-
|
| 1349 |
-
async function loadTrending() {
|
| 1350 |
-
const section = document.getElementById('trendingSection');
|
| 1351 |
-
const container = document.getElementById('trendingContainer');
|
| 1352 |
-
if (!section || !container) return;
|
| 1353 |
-
try {
|
| 1354 |
-
const resp = await window.EdgeFunctions.trending.list(10, 24);
|
| 1355 |
-
if (!resp.success || !resp.products || resp.products.length === 0) return;
|
| 1356 |
-
section.classList.remove('hidden');
|
| 1357 |
-
document.getElementById('trendingRefreshedAt').textContent = 'Updated ' + timeAgo(resp.refreshed_at);
|
| 1358 |
-
container.innerHTML = resp.products.map(p => `
|
| 1359 |
-
<a href="product.html?id=${p.id}" class="bg-white rounded-2xl border border-ink-100 shadow-card overflow-hidden lift block">
|
| 1360 |
-
${p.image_url
|
| 1361 |
-
? `<img src="${p.image_url}" class="w-full aspect-square object-cover">`
|
| 1362 |
-
: `<div class="w-full aspect-square bg-ink-100 flex items-center justify-center text-3xl text-ink-300"><i class="fas fa-box"></i></div>`}
|
| 1363 |
-
<div class="p-2">
|
| 1364 |
-
<div class="font-bold text-sm line-clamp-1">${escapeHtml(p.name)}</div>
|
| 1365 |
-
<div class="text-cyan-600 font-extrabold text-sm mt-0.5">$${Number(p.price).toFixed(2)}</div>
|
| 1366 |
-
<div class="flex items-center gap-2 text-[10px] text-ink-500 mt-1">
|
| 1367 |
-
<span><i class="fas fa-eye"></i> ${p.trending.views}</span>
|
| 1368 |
-
<span><i class="fas fa-share"></i> ${p.trending.shares}</span>
|
| 1369 |
-
<span><i class="fas fa-shopping-bag"></i> ${p.trending.purchases}</span>
|
| 1370 |
-
</div>
|
| 1371 |
-
</div>
|
| 1372 |
-
</a>`).join('');
|
| 1373 |
-
} catch (e) {
|
| 1374 |
-
console.error('trending error:', e);
|
| 1375 |
-
}
|
| 1376 |
-
}
|
| 1377 |
-
|
| 1378 |
-
async function loadDiscoverForYou() {
|
| 1379 |
-
const section = document.getElementById('discoverSection');
|
| 1380 |
-
const container = document.getElementById('discoverContainer');
|
| 1381 |
-
if (!section || !container) return;
|
| 1382 |
-
// Only show for logged-in users
|
| 1383 |
-
if (!window.currentUser) return;
|
| 1384 |
-
try {
|
| 1385 |
-
const resp = await window.EdgeFunctions.discover.recommend(10);
|
| 1386 |
-
if (!resp.success || !resp.recommendations || resp.recommendations.length === 0) return;
|
| 1387 |
-
section.classList.remove('hidden');
|
| 1388 |
-
container.innerHTML = resp.recommendations.map(r => {
|
| 1389 |
-
const p = r.product;
|
| 1390 |
-
return `
|
| 1391 |
-
<a href="product.html?id=${p.id}" onclick="logDiscoverView(${p.id})" class="bg-white rounded-2xl border border-violet-200 shadow-card overflow-hidden lift block relative">
|
| 1392 |
-
${p.image_url
|
| 1393 |
-
? `<img src="${p.image_url}" class="w-full aspect-square object-cover">`
|
| 1394 |
-
: `<div class="w-full aspect-square bg-violet-50 flex items-center justify-center text-3xl text-violet-300"><i class="fas fa-box"></i></div>`}
|
| 1395 |
-
<div class="absolute top-1 right-1 bg-violet-600 text-white text-[9px] font-bold px-1.5 py-0.5 rounded">${escapeHtml(r.signal.replace(/_/g,' '))}</div>
|
| 1396 |
-
<div class="p-2">
|
| 1397 |
-
<div class="font-bold text-sm line-clamp-1">${escapeHtml(p.name)}</div>
|
| 1398 |
-
<div class="text-cyan-600 font-extrabold text-sm mt-0.5">$${Number(p.price).toFixed(2)}</div>
|
| 1399 |
-
<div class="text-[10px] text-ink-500 mt-1 line-clamp-2">${escapeHtml(r.reason)}</div>
|
| 1400 |
-
</div>
|
| 1401 |
-
</a>`;
|
| 1402 |
-
}).join('');
|
| 1403 |
-
} catch (e) {
|
| 1404 |
-
console.error('discover error:', e);
|
| 1405 |
-
}
|
| 1406 |
-
}
|
| 1407 |
-
|
| 1408 |
-
async function logDiscoverView(productId) {
|
| 1409 |
-
window.EdgeFunctions.discover.logView(productId).catch(() => {});
|
| 1410 |
-
}
|
| 1411 |
-
|
| 1412 |
-
// Story viewer (simple modal-like full-screen viewer)
|
| 1413 |
-
function openStoryViewer(stories, startIndex) {
|
| 1414 |
-
let idx = startIndex || 0;
|
| 1415 |
-
const overlay = document.createElement('div');
|
| 1416 |
-
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.95);z-index:1000;display:flex;align-items:center;justify-content:center;padding:1rem;';
|
| 1417 |
-
const render = () => {
|
| 1418 |
-
const s = stories[idx];
|
| 1419 |
-
if (!s) { overlay.remove(); return; }
|
| 1420 |
-
overlay.innerHTML = `
|
| 1421 |
-
<button onclick="this.parentElement.remove()" style="position:absolute;top:1rem;right:1rem;background:rgba(255,255,255,0.2);border:none;color:#fff;width:36px;height:36px;border-radius:50%;font-size:1.2rem;cursor:pointer;">×</button>
|
| 1422 |
-
<div style="max-width:420px;width:100%;text-align:center;">
|
| 1423 |
-
<div style="margin-bottom:0.5rem;color:#fff;font-size:0.85rem;">${idx + 1} / ${stories.length}</div>
|
| 1424 |
-
${s.video_url
|
| 1425 |
-
? `<video src="${s.video_url}" autoplay style="max-width:100%;max-height:70vh;border-radius:12px;" controls></video>`
|
| 1426 |
-
: s.image_url
|
| 1427 |
-
? `<img src="${s.image_url}" style="max-width:100%;max-height:70vh;border-radius:12px;">`
|
| 1428 |
-
: `<div style="background:#fff;border-radius:12px;padding:2rem;"><i class="fas fa-bullhorn text-3xl text-cyan-500"></i></div>`}
|
| 1429 |
-
<div style="color:#fff;margin-top:1rem;font-weight:600;">${escapeHtml(s.title || '')}</div>
|
| 1430 |
-
${s.title ? `<div style="color:#cbd5e1;margin-top:0.25rem;font-size:0.85rem;">${escapeHtml(s.title)}</div>` : ''}
|
| 1431 |
-
<div style="margin-top:1rem;display:flex;gap:0.5rem;justify-content:center;">
|
| 1432 |
-
${idx > 0 ? '<button onclick="window._storyPrev()" style="background:rgba(255,255,255,0.2);color:#fff;border:none;padding:0.5rem 1rem;border-radius:8px;cursor:pointer;">← Prev</button>' : ''}
|
| 1433 |
-
${idx < stories.length - 1 ? '<button onclick="window._storyNext()" style="background:#00d4ff;color:#0f172a;border:none;padding:0.5rem 1rem;border-radius:8px;font-weight:700;cursor:pointer;">Next →</button>' : '<button onclick="this.parentElement.parentElement.parentElement.remove()" style="background:rgba(255,255,255,0.2);color:#fff;border:none;padding:0.5rem 1rem;border-radius:8px;cursor:pointer;">Done</button>'}
|
| 1434 |
-
</div>
|
| 1435 |
-
</div>`;
|
| 1436 |
-
// Mark as seen
|
| 1437 |
-
window.EdgeFunctions.stories.get(s.id).catch(() => {});
|
| 1438 |
-
};
|
| 1439 |
-
window._storyNext = () => { idx = Math.min(idx + 1, stories.length - 1); render(); };
|
| 1440 |
-
window._storyPrev = () => { idx = Math.max(idx - 1, 0); render(); };
|
| 1441 |
-
render();
|
| 1442 |
-
document.body.appendChild(overlay);
|
| 1443 |
-
// Click outside to close
|
| 1444 |
-
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
| 1445 |
-
}
|
| 1446 |
-
</script>
|
| 1447 |
-
</body>
|
| 1448 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/ai-assistant.js
DELETED
|
@@ -1,301 +0,0 @@
|
|
| 1 |
-
// AI Shopping Assistant
|
| 2 |
-
class AIShoppingAssistant {
|
| 3 |
-
constructor() {
|
| 4 |
-
this.assistantWindow = null;
|
| 5 |
-
this.createAssistantWindow();
|
| 6 |
-
this.bindEvents();
|
| 7 |
-
}
|
| 8 |
-
|
| 9 |
-
createAssistantWindow() {
|
| 10 |
-
// Create modal HTML
|
| 11 |
-
const modalHTML = `
|
| 12 |
-
<div id="ai-assistant-modal" class="ai-modal">
|
| 13 |
-
<div class="ai-modal-content">
|
| 14 |
-
<div class="ai-modal-header">
|
| 15 |
-
<h3><i class="fas fa-robot"></i> Shopping Assistant</h3>
|
| 16 |
-
<button class="close-modal">×</button>
|
| 17 |
-
</div>
|
| 18 |
-
<div class="ai-chat-container">
|
| 19 |
-
<div class="ai-messages" id="aiMessages">
|
| 20 |
-
<div class="ai-message">
|
| 21 |
-
Hello! I'm your shopping assistant. Tell me what you're looking for, and I'll help you find the perfect product.
|
| 22 |
-
</div>
|
| 23 |
-
</div>
|
| 24 |
-
<div class="ai-input">
|
| 25 |
-
<input type="text" id="userInput" placeholder="Describe what you're looking for...">
|
| 26 |
-
<button id="sendMessage"><i class="fas fa-paper-plane"></i></button>
|
| 27 |
-
</div>
|
| 28 |
-
</div>
|
| 29 |
-
</div>
|
| 30 |
-
</div>`;
|
| 31 |
-
|
| 32 |
-
// Add modal to body
|
| 33 |
-
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
| 34 |
-
this.assistantWindow = document.getElementById('ai-assistant-modal');
|
| 35 |
-
|
| 36 |
-
// Add styles
|
| 37 |
-
const styles = `
|
| 38 |
-
.ai-modal {
|
| 39 |
-
display: none;
|
| 40 |
-
position: fixed;
|
| 41 |
-
top: 0;
|
| 42 |
-
left: 0;
|
| 43 |
-
width: 100%;
|
| 44 |
-
height: 100%;
|
| 45 |
-
background-color: rgba(0,0,0,0.5);
|
| 46 |
-
z-index: 1001;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
.ai-modal-content {
|
| 50 |
-
position: fixed;
|
| 51 |
-
bottom: 2rem;
|
| 52 |
-
right: 2rem;
|
| 53 |
-
width: 350px;
|
| 54 |
-
height: 500px;
|
| 55 |
-
background-color: white;
|
| 56 |
-
border-radius: 12px;
|
| 57 |
-
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
| 58 |
-
display: flex;
|
| 59 |
-
flex-direction: column;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
.ai-modal-header {
|
| 63 |
-
padding: 1rem;
|
| 64 |
-
background-color: #00d4ff;
|
| 65 |
-
color: white;
|
| 66 |
-
border-radius: 12px 12px 0 0;
|
| 67 |
-
display: flex;
|
| 68 |
-
justify-content: space-between;
|
| 69 |
-
align-items: center;
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
.ai-modal-header h3 {
|
| 73 |
-
display: flex;
|
| 74 |
-
align-items: center;
|
| 75 |
-
gap: 0.5rem;
|
| 76 |
-
margin: 0;
|
| 77 |
-
font-size: 1.1rem;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
.close-modal {
|
| 81 |
-
background: none;
|
| 82 |
-
border: none;
|
| 83 |
-
color: white;
|
| 84 |
-
font-size: 1.5rem;
|
| 85 |
-
cursor: pointer;
|
| 86 |
-
padding: 0;
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
.ai-chat-container {
|
| 90 |
-
flex: 1;
|
| 91 |
-
display: flex;
|
| 92 |
-
flex-direction: column;
|
| 93 |
-
padding: 1rem;
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
.ai-messages {
|
| 97 |
-
flex: 1;
|
| 98 |
-
overflow-y: auto;
|
| 99 |
-
padding: 1rem;
|
| 100 |
-
display: flex;
|
| 101 |
-
flex-direction: column;
|
| 102 |
-
gap: 1rem;
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
.ai-message, .user-message {
|
| 106 |
-
max-width: 80%;
|
| 107 |
-
padding: 0.8rem;
|
| 108 |
-
border-radius: 12px;
|
| 109 |
-
font-size: 0.9rem;
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
.ai-message {
|
| 113 |
-
background-color: #f0f2f5;
|
| 114 |
-
align-self: flex-start;
|
| 115 |
-
border-bottom-left-radius: 4px;
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
.user-message {
|
| 119 |
-
background-color: #00d4ff;
|
| 120 |
-
color: white;
|
| 121 |
-
align-self: flex-end;
|
| 122 |
-
border-bottom-right-radius: 4px;
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
.ai-input {
|
| 126 |
-
display: flex;
|
| 127 |
-
gap: 0.5rem;
|
| 128 |
-
padding: 1rem;
|
| 129 |
-
background-color: white;
|
| 130 |
-
border-top: 1px solid #eee;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
.ai-input input {
|
| 134 |
-
flex: 1;
|
| 135 |
-
padding: 0.8rem;
|
| 136 |
-
border: 1px solid #ddd;
|
| 137 |
-
border-radius: 20px;
|
| 138 |
-
font-size: 0.9rem;
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
.ai-input button {
|
| 142 |
-
background-color: #00d4ff;
|
| 143 |
-
color: white;
|
| 144 |
-
border: none;
|
| 145 |
-
border-radius: 50%;
|
| 146 |
-
width: 40px;
|
| 147 |
-
height: 40px;
|
| 148 |
-
cursor: pointer;
|
| 149 |
-
display: flex;
|
| 150 |
-
align-items: center;
|
| 151 |
-
justify-content: center;
|
| 152 |
-
transition: background-color 0.3s;
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
.ai-input button:hover {
|
| 156 |
-
background-color: #d63d40;
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
.product-suggestion {
|
| 160 |
-
background-color: white;
|
| 161 |
-
border: 1px solid #ddd;
|
| 162 |
-
border-radius: 8px;
|
| 163 |
-
padding: 0.8rem;
|
| 164 |
-
margin-top: 0.5rem;
|
| 165 |
-
cursor: pointer;
|
| 166 |
-
transition: transform 0.2s;
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
.product-suggestion:hover {
|
| 170 |
-
transform: translateY(-2px);
|
| 171 |
-
}
|
| 172 |
-
`;
|
| 173 |
-
|
| 174 |
-
const styleSheet = document.createElement('style');
|
| 175 |
-
styleSheet.textContent = styles;
|
| 176 |
-
document.head.appendChild(styleSheet);
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
bindEvents() {
|
| 180 |
-
// Open assistant
|
| 181 |
-
document.querySelector('.ai-button').addEventListener('click', () => this.open());
|
| 182 |
-
|
| 183 |
-
// Close assistant
|
| 184 |
-
document.querySelector('.close-modal').addEventListener('click', () => this.close());
|
| 185 |
-
|
| 186 |
-
// Send message
|
| 187 |
-
document.getElementById('sendMessage').addEventListener('click', () => this.sendMessage());
|
| 188 |
-
document.getElementById('userInput').addEventListener('keypress', (e) => {
|
| 189 |
-
if (e.key === 'Enter') this.sendMessage();
|
| 190 |
-
});
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
open() {
|
| 194 |
-
this.assistantWindow.style.display = 'block';
|
| 195 |
-
document.getElementById('userInput').focus();
|
| 196 |
-
}
|
| 197 |
-
|
| 198 |
-
close() {
|
| 199 |
-
this.assistantWindow.style.display = 'none';
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
-
sendMessage() {
|
| 203 |
-
const input = document.getElementById('userInput');
|
| 204 |
-
const message = input.value.trim();
|
| 205 |
-
|
| 206 |
-
if (message) {
|
| 207 |
-
this.addMessage(message, 'user');
|
| 208 |
-
input.value = '';
|
| 209 |
-
|
| 210 |
-
// Process the message and generate a response
|
| 211 |
-
this.processUserInput(message);
|
| 212 |
-
}
|
| 213 |
-
}
|
| 214 |
-
|
| 215 |
-
addMessage(message, sender) {
|
| 216 |
-
const messagesContainer = document.getElementById('aiMessages');
|
| 217 |
-
const messageDiv = document.createElement('div');
|
| 218 |
-
messageDiv.className = sender === 'user' ? 'user-message' : 'ai-message';
|
| 219 |
-
messageDiv.textContent = message;
|
| 220 |
-
messagesContainer.appendChild(messageDiv);
|
| 221 |
-
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
-
async processUserInput(message) {
|
| 225 |
-
// Simulate AI processing
|
| 226 |
-
const keywords = this.extractKeywords(message);
|
| 227 |
-
const suggestions = this.findRelevantProducts(keywords);
|
| 228 |
-
|
| 229 |
-
// Simulate typing delay
|
| 230 |
-
await this.delay(1000);
|
| 231 |
-
|
| 232 |
-
// Generate and display response
|
| 233 |
-
const response = this.generateResponse(message, suggestions);
|
| 234 |
-
this.addMessage(response, 'ai');
|
| 235 |
-
|
| 236 |
-
// Display product suggestions if any
|
| 237 |
-
if (suggestions.length > 0) {
|
| 238 |
-
await this.delay(500);
|
| 239 |
-
this.displayProductSuggestions(suggestions);
|
| 240 |
-
}
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
extractKeywords(message) {
|
| 244 |
-
// Simple keyword extraction (can be enhanced with NLP)
|
| 245 |
-
return message.toLowerCase()
|
| 246 |
-
.replace(/[^\w\s]/g, '')
|
| 247 |
-
.split(' ')
|
| 248 |
-
.filter(word => word.length > 2);
|
| 249 |
-
}
|
| 250 |
-
|
| 251 |
-
findRelevantProducts(keywords) {
|
| 252 |
-
// This should be replaced with actual product filtering logic
|
| 253 |
-
return products.filter(product =>
|
| 254 |
-
keywords.some(keyword =>
|
| 255 |
-
product.name.toLowerCase().includes(keyword) ||
|
| 256 |
-
product.description.toLowerCase().includes(keyword) ||
|
| 257 |
-
product.category.toLowerCase().includes(keyword)
|
| 258 |
-
)
|
| 259 |
-
);
|
| 260 |
-
}
|
| 261 |
-
|
| 262 |
-
generateResponse(message, suggestions) {
|
| 263 |
-
if (suggestions.length === 0) {
|
| 264 |
-
return "I couldn't find any products matching your description. Could you please provide more details about what you're looking for?";
|
| 265 |
-
}
|
| 266 |
-
|
| 267 |
-
return `I found ${suggestions.length} products that might interest you. Here are some suggestions:`;
|
| 268 |
-
}
|
| 269 |
-
|
| 270 |
-
displayProductSuggestions(suggestions) {
|
| 271 |
-
const messagesContainer = document.getElementById('aiMessages');
|
| 272 |
-
|
| 273 |
-
suggestions.forEach(product => {
|
| 274 |
-
const suggestionDiv = document.createElement('div');
|
| 275 |
-
suggestionDiv.className = 'product-suggestion';
|
| 276 |
-
suggestionDiv.innerHTML = `
|
| 277 |
-
<strong>${product.name}</strong>
|
| 278 |
-
<p>${product.description}</p>
|
| 279 |
-
<p class="price">₦${product.price}</p>
|
| 280 |
-
`;
|
| 281 |
-
|
| 282 |
-
suggestionDiv.addEventListener('click', () => {
|
| 283 |
-
// Navigate to product details or add to cart
|
| 284 |
-
this.addMessage(`Would you like to add ${product.name} to your cart?`, 'ai');
|
| 285 |
-
});
|
| 286 |
-
|
| 287 |
-
messagesContainer.appendChild(suggestionDiv);
|
| 288 |
-
});
|
| 289 |
-
|
| 290 |
-
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| 291 |
-
}
|
| 292 |
-
|
| 293 |
-
delay(ms) {
|
| 294 |
-
return new Promise(resolve => setTimeout(resolve, ms));
|
| 295 |
-
}
|
| 296 |
-
}
|
| 297 |
-
|
| 298 |
-
// Initialize AI Shopping Assistant when the page loads
|
| 299 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 300 |
-
window.aiAssistant = new AIShoppingAssistant();
|
| 301 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/ai-buyer-assistant.js
DELETED
|
@@ -1,962 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* Cellex AI Buyer Assistant - "Cellex"
|
| 3 |
-
* FREE & OPEN SOURCE - No API keys required!
|
| 4 |
-
*
|
| 5 |
-
* Supports TWO modes:
|
| 6 |
-
* 1. Widget mode (index.html) — creates floating chat widget
|
| 7 |
-
* 2. Full-page mode (ai-chat.html) — attaches to existing page UI
|
| 8 |
-
*
|
| 9 |
-
* The AI backend handles natural language understanding
|
| 10 |
-
* Frontend just sends context and executes actions
|
| 11 |
-
*/
|
| 12 |
-
|
| 13 |
-
(function() {
|
| 14 |
-
'use strict';
|
| 15 |
-
|
| 16 |
-
const CONFIG = {
|
| 17 |
-
// AI chat now goes through our Supabase Edge Function (server-side).
|
| 18 |
-
// The HF Space URL, AI model, and system prompt are all hidden from the frontend.
|
| 19 |
-
// Edge function: supabase/functions/ai-chat/index.ts
|
| 20 |
-
sessionKey: 'eeshamart_ai_session',
|
| 21 |
-
debug: true
|
| 22 |
-
};
|
| 23 |
-
|
| 24 |
-
// Detect full-page mode: ai-chat.html has #chatContainer
|
| 25 |
-
const isFullPage = !!document.getElementById('chatContainer');
|
| 26 |
-
|
| 27 |
-
// Inject shared product + action-result styles ONCE, regardless of mode.
|
| 28 |
-
// Both widget mode and full-page mode use the same .ai-product-card / .ai-action-result
|
| 29 |
-
// classes so users get a consistent experience across surfaces.
|
| 30 |
-
(function injectSharedStyles() {
|
| 31 |
-
if (document.getElementById('ai-shared-styles')) return;
|
| 32 |
-
const style = document.createElement('style');
|
| 33 |
-
style.id = 'ai-shared-styles';
|
| 34 |
-
style.textContent = `
|
| 35 |
-
.ai-product-card{background:white;border-radius:10px;padding:10px;margin-top:10px;box-shadow:0 2px 8px rgba(0,0,0,.06);display:flex;gap:10px;cursor:pointer;transition:all .2s;border:2px solid transparent}
|
| 36 |
-
.ai-product-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.1);border-color:#00d4ff}
|
| 37 |
-
.ai-product-img{width:64px;height:64px;border-radius:8px;object-fit:cover;background:#f1f5f9;flex-shrink:0}
|
| 38 |
-
.ai-product-info{flex:1;min-width:0}
|
| 39 |
-
.ai-product-name{font-weight:600;font-size:12px;margin-bottom:2px;color:#1e293b}
|
| 40 |
-
.ai-product-price{color:#0891b2;font-weight:700;font-size:13px}
|
| 41 |
-
.ai-product-meta{font-size:10px;color:#64748b;margin-top:2px}
|
| 42 |
-
.ai-product-actions{display:flex;gap:4px;margin-top:6px}
|
| 43 |
-
.ai-action-btn{padding:4px 8px;border-radius:4px;border:none;font-size:10px;font-weight:600;cursor:pointer}
|
| 44 |
-
.ai-action-btn-primary{background:linear-gradient(135deg,#00d4ff,#00d4ff);color:#0f172a}
|
| 45 |
-
.ai-action-btn-secondary{background:#f1f5f9;color:#475569}
|
| 46 |
-
.ai-action-btn:hover{transform:scale(1.05)}
|
| 47 |
-
.ai-action-result{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f0fdf4;border-radius:8px;margin-top:8px;font-size:12px;color:#166534;border:1px solid #bbf7d0}
|
| 48 |
-
.ai-action-result.error{background:#fef2f2;color:#991b1b;border-color:#fecaca}
|
| 49 |
-
.ai-action-result.warning{background:#fffbeb;color:#0e7490;border-color:#a5f3fc}
|
| 50 |
-
`;
|
| 51 |
-
document.head.appendChild(style);
|
| 52 |
-
})();
|
| 53 |
-
let isOpen = false;
|
| 54 |
-
let isLoading = false;
|
| 55 |
-
let conversationHistory = [];
|
| 56 |
-
let sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
| 57 |
-
let container = null;
|
| 58 |
-
let selectedImage = null;
|
| 59 |
-
let context = {
|
| 60 |
-
lastShownProducts: [],
|
| 61 |
-
cartItems: [],
|
| 62 |
-
user: null
|
| 63 |
-
};
|
| 64 |
-
|
| 65 |
-
const CATEGORIES = [
|
| 66 |
-
{ name: 'Electronics', icon: 'fa-laptop', query: 'Show me electronics' },
|
| 67 |
-
{ name: 'Fashion', icon: 'fa-tshirt', query: 'Show me fashion' },
|
| 68 |
-
{ name: 'Home', icon: 'fa-couch', query: 'Show me home products' },
|
| 69 |
-
{ name: 'Beauty', icon: 'fa-spa', query: 'Show me beauty products' },
|
| 70 |
-
{ name: 'Sports', icon: 'fa-futbol', query: 'Show me sports gear' },
|
| 71 |
-
{ name: 'Books', icon: 'fa-book', query: 'Show me books' }
|
| 72 |
-
];
|
| 73 |
-
|
| 74 |
-
document.addEventListener('DOMContentLoaded', init);
|
| 75 |
-
|
| 76 |
-
function init() {
|
| 77 |
-
if (isFullPage) {
|
| 78 |
-
initFullPage();
|
| 79 |
-
} else {
|
| 80 |
-
setTimeout(createWidget, 500);
|
| 81 |
-
}
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
// ============================================
|
| 85 |
-
// FULL-PAGE MODE (ai-chat.html)
|
| 86 |
-
// ============================================
|
| 87 |
-
|
| 88 |
-
function initFullPage() {
|
| 89 |
-
console.log('[Cellex AI] Full-page mode activated');
|
| 90 |
-
|
| 91 |
-
// Wire suggestion chips to sendMessage
|
| 92 |
-
document.querySelectorAll('.chip[data-query], .chip[onclick]').forEach(btn => {
|
| 93 |
-
// Chips already have onclick="sendSuggestion('...')" in HTML
|
| 94 |
-
});
|
| 95 |
-
|
| 96 |
-
// Wire image upload button
|
| 97 |
-
const fileInput = document.getElementById('ai-file-input');
|
| 98 |
-
if (fileInput) {
|
| 99 |
-
fileInput.addEventListener('change', handleFileSelect);
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
// Wire image remove button
|
| 103 |
-
const removeBtn = document.getElementById('ai-remove-image');
|
| 104 |
-
if (removeBtn) {
|
| 105 |
-
removeBtn.addEventListener('click', removeSelectedImage);
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
// Make sendMessage available globally (needed by onclick/onkeypress in HTML)
|
| 109 |
-
window.sendMessage = sendMessage;
|
| 110 |
-
|
| 111 |
-
// Make sendSuggestion available globally
|
| 112 |
-
window.sendSuggestion = function(text) {
|
| 113 |
-
document.getElementById('chatInput').value = text;
|
| 114 |
-
sendMessage();
|
| 115 |
-
};
|
| 116 |
-
|
| 117 |
-
// Make clearChat available globally
|
| 118 |
-
window.clearChat = clearConversation;
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
function fullPage_addMessage(role, content, data = {}) {
|
| 122 |
-
const chatContainer = document.getElementById('chatContainer');
|
| 123 |
-
if (!chatContainer) return;
|
| 124 |
-
const now = new Date();
|
| 125 |
-
const time = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
| 126 |
-
const isUser = role === 'user';
|
| 127 |
-
const parsed = content
|
| 128 |
-
.replace(/\*\*(.*?)\*\*/g, '<strong>₦1</strong>')
|
| 129 |
-
.replace(/\n/g, '<br>');
|
| 130 |
-
|
| 131 |
-
// Build image HTML if provided
|
| 132 |
-
let imageHtml = '';
|
| 133 |
-
if (data.image) {
|
| 134 |
-
imageHtml = `<div style="margin-bottom:8px"><img src="${data.image}" alt="Shared image" style="max-width:200px;max-height:150px;border-radius:8px;object-fit:cover;box-shadow:0 2px 8px rgba(0,0,0,.1)"></div>`;
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
if (isUser) {
|
| 138 |
-
const div = document.createElement('div');
|
| 139 |
-
div.className = 'flex justify-end mb-3';
|
| 140 |
-
div.innerHTML = `
|
| 141 |
-
<div class="message-bubble bg-gradient-to-br from-orange-400 to-cyan-600 rounded-2xl rounded-tr-md p-3.5 shadow-sm max-w-[80%]">
|
| 142 |
-
${imageHtml}
|
| 143 |
-
<p class="text-sm text-white leading-relaxed">${parsed}</p>
|
| 144 |
-
<p class="text-[10px] text-orange-100 mt-1.5 text-right">${time}</p>
|
| 145 |
-
</div>`;
|
| 146 |
-
chatContainer.appendChild(div);
|
| 147 |
-
} else {
|
| 148 |
-
const div = document.createElement('div');
|
| 149 |
-
div.className = 'flex items-start gap-2.5 mb-3';
|
| 150 |
-
div.innerHTML = `
|
| 151 |
-
<div class="w-8 h-8 bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 shadow-sm">
|
| 152 |
-
<i class="fas fa-robot text-white text-xs"></i>
|
| 153 |
-
</div>
|
| 154 |
-
<div class="message-bubble bg-white rounded-2xl rounded-tl-md p-3.5 shadow-sm border border-gray-100 max-w-[80%]">
|
| 155 |
-
${imageHtml}
|
| 156 |
-
<div class="text-sm text-gray-700 leading-relaxed">${parsed}</div>
|
| 157 |
-
${renderProductsWidget(data.products)}
|
| 158 |
-
${renderActionResultWidget(data.actionResult)}
|
| 159 |
-
<p class="text-[10px] text-gray-400 mt-1.5">${time}</p>
|
| 160 |
-
</div>`;
|
| 161 |
-
chatContainer.appendChild(div);
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
fullPageScrollToBottom();
|
| 165 |
-
addProductHandlers();
|
| 166 |
-
|
| 167 |
-
// Hide suggestion chips after first interaction
|
| 168 |
-
if (conversationHistory.length > 0 || role === 'user') {
|
| 169 |
-
const chips = document.getElementById('suggestionChips');
|
| 170 |
-
if (chips) chips.style.display = 'none';
|
| 171 |
-
}
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
function fullPage_showTypingIndicator() {
|
| 175 |
-
const chatContainer = document.getElementById('chatContainer');
|
| 176 |
-
if (!chatContainer) return;
|
| 177 |
-
const div = document.createElement('div');
|
| 178 |
-
div.id = 'typingIndicator';
|
| 179 |
-
div.className = 'flex items-start gap-2.5 mb-3';
|
| 180 |
-
div.innerHTML = `
|
| 181 |
-
<div class="w-8 h-8 bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 shadow-sm">
|
| 182 |
-
<i class="fas fa-robot text-white text-xs"></i>
|
| 183 |
-
</div>
|
| 184 |
-
<div class="bg-white rounded-2xl rounded-tl-md p-3.5 shadow-sm border border-gray-100">
|
| 185 |
-
<div class="flex items-center gap-1 py-1">
|
| 186 |
-
<span class="typing-dot"></span>
|
| 187 |
-
<span class="typing-dot"></span>
|
| 188 |
-
<span class="typing-dot"></span>
|
| 189 |
-
</div>
|
| 190 |
-
</div>`;
|
| 191 |
-
chatContainer.appendChild(div);
|
| 192 |
-
fullPageScrollToBottom();
|
| 193 |
-
}
|
| 194 |
-
|
| 195 |
-
function fullPage_removeTypingIndicator() {
|
| 196 |
-
document.getElementById('typingIndicator')?.remove();
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
function fullPage_updateTypingText(text) {
|
| 200 |
-
const indicator = document.getElementById('typingIndicator');
|
| 201 |
-
if (indicator) {
|
| 202 |
-
const bubble = indicator.querySelector('.bg-white');
|
| 203 |
-
if (bubble) {
|
| 204 |
-
bubble.innerHTML = `<p class="text-sm text-cyan-500">${text}</p>`;
|
| 205 |
-
}
|
| 206 |
-
}
|
| 207 |
-
}
|
| 208 |
-
|
| 209 |
-
function fullPageScrollToBottom() {
|
| 210 |
-
const chatContainer = document.getElementById('chatContainer');
|
| 211 |
-
if (chatContainer) {
|
| 212 |
-
requestAnimationFrame(() => {
|
| 213 |
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 214 |
-
});
|
| 215 |
-
}
|
| 216 |
-
}
|
| 217 |
-
|
| 218 |
-
// ============================================
|
| 219 |
-
// WIDGET MODE (index.html) — original code
|
| 220 |
-
// ============================================
|
| 221 |
-
|
| 222 |
-
function createWidget() {
|
| 223 |
-
container = document.createElement('div');
|
| 224 |
-
container.id = 'ai-buyer-assistant';
|
| 225 |
-
container.innerHTML = getWidgetHTML();
|
| 226 |
-
document.body.appendChild(container);
|
| 227 |
-
|
| 228 |
-
document.getElementById('ai-toggle-btn').addEventListener('click', toggleWidget);
|
| 229 |
-
document.getElementById('ai-close-btn')?.addEventListener('click', toggleWidget);
|
| 230 |
-
document.getElementById('ai-send-btn').addEventListener('click', sendMessage);
|
| 231 |
-
document.getElementById('ai-input').addEventListener('keypress', handleKeyPress);
|
| 232 |
-
document.getElementById('ai-file-input').addEventListener('change', handleFileSelect);
|
| 233 |
-
document.getElementById('ai-clear-btn')?.addEventListener('click', clearConversation);
|
| 234 |
-
|
| 235 |
-
container.querySelectorAll('.ai-category-btn').forEach(btn => {
|
| 236 |
-
btn.addEventListener('click', () => {
|
| 237 |
-
document.getElementById('ai-input').value = btn.dataset.query;
|
| 238 |
-
sendMessage();
|
| 239 |
-
});
|
| 240 |
-
});
|
| 241 |
-
|
| 242 |
-
document.getElementById('ai-remove-image')?.addEventListener('click', removeSelectedImage);
|
| 243 |
-
}
|
| 244 |
-
|
| 245 |
-
function getWidgetHTML() {
|
| 246 |
-
return `
|
| 247 |
-
<style>${getWidgetStyles()}</style>
|
| 248 |
-
|
| 249 |
-
<button id="ai-toggle-btn" class="ai-toggle-btn" title="Open AI Shopping Assistant">
|
| 250 |
-
<i class="fas fa-wand-magic-sparkles"></i>
|
| 251 |
-
<span class="ai-pulse"></span>
|
| 252 |
-
</button>
|
| 253 |
-
|
| 254 |
-
<div id="ai-widget" class="ai-widget ai-hidden">
|
| 255 |
-
<div class="ai-header">
|
| 256 |
-
<div class="ai-header-left">
|
| 257 |
-
<div class="ai-logo"><i class="fas fa-wand-magic-sparkles"></i></div>
|
| 258 |
-
<div class="ai-header-info">
|
| 259 |
-
<h3>Cellex AI</h3>
|
| 260 |
-
<span class="ai-status">
|
| 261 |
-
<span class="ai-status-dot"></span>
|
| 262 |
-
<span id="ai-status-text">FREE & Open Source</span>
|
| 263 |
-
</span>
|
| 264 |
-
</div>
|
| 265 |
-
</div>
|
| 266 |
-
<div class="ai-header-actions">
|
| 267 |
-
<button id="ai-clear-btn" class="ai-header-btn" title="Clear"><i class="fas fa-refresh"></i></button>
|
| 268 |
-
<button id="ai-close-btn" class="ai-header-btn" title="Close"><i class="fas fa-times"></i></button>
|
| 269 |
-
</div>
|
| 270 |
-
</div>
|
| 271 |
-
|
| 272 |
-
<div id="ai-messages" class="ai-messages"></div>
|
| 273 |
-
|
| 274 |
-
<div id="ai-image-preview" class="ai-image-preview ai-hidden">
|
| 275 |
-
<div class="ai-preview-container">
|
| 276 |
-
<img id="ai-preview-img" src="" alt="Preview">
|
| 277 |
-
<button id="ai-remove-image" class="ai-remove-image"><i class="fas fa-times"></i></button>
|
| 278 |
-
</div>
|
| 279 |
-
</div>
|
| 280 |
-
|
| 281 |
-
<div id="ai-quick-actions" class="ai-quick-actions">
|
| 282 |
-
<p class="ai-quick-title">Try these:</p>
|
| 283 |
-
<div class="ai-categories">
|
| 284 |
-
${CATEGORIES.map(cat => `
|
| 285 |
-
<button class="ai-category-btn" data-query="${cat.query}">
|
| 286 |
-
<i class="fas ${cat.icon}"></i> ${cat.name}
|
| 287 |
-
</button>
|
| 288 |
-
`).join('')}
|
| 289 |
-
</div>
|
| 290 |
-
</div>
|
| 291 |
-
|
| 292 |
-
<div class="ai-input-area">
|
| 293 |
-
<div class="ai-input-container">
|
| 294 |
-
<input type="file" id="ai-file-input" accept="image/*" hidden>
|
| 295 |
-
<button id="ai-upload-btn" class="ai-upload-btn" onclick="document.getElementById('ai-file-input').click()">
|
| 296 |
-
<i class="fas fa-camera"></i>
|
| 297 |
-
</button>
|
| 298 |
-
<input type="text" id="ai-input" placeholder="Tell me what you need..." autocomplete="off">
|
| 299 |
-
<button id="ai-send-btn" class="ai-send-btn"><i class="fas fa-paper-plane"></i></button>
|
| 300 |
-
</div>
|
| 301 |
-
</div>
|
| 302 |
-
</div>
|
| 303 |
-
`;
|
| 304 |
-
}
|
| 305 |
-
|
| 306 |
-
function getWidgetStyles() {
|
| 307 |
-
return `
|
| 308 |
-
.ai-toggle-btn{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,#00d4ff,#00d4ff);border:none;cursor:pointer;box-shadow:0 4px 20px rgba(245,158,11,0.4);z-index:9998;display:flex;align-items:center;justify-content:center;transition:all .3s;color:#0f172a;font-size:22px}
|
| 309 |
-
.ai-toggle-btn:hover{transform:scale(1.1);box-shadow:0 6px 30px rgba(245,158,11,0.5)}
|
| 310 |
-
.ai-pulse{position:absolute;top:-2px;right:-2px;width:14px;height:14px;background:#22c55e;border-radius:50%;border:2px solid white;animation:pulse 2s infinite}
|
| 311 |
-
@keyframes pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.2);opacity:.7}}
|
| 312 |
-
.ai-widget{position:fixed;bottom:96px;right:24px;width:380px;max-width:calc(100vw - 48px);height:560px;max-height:calc(100vh - 140px);background:white;border-radius:16px;box-shadow:0 10px 50px rgba(0,0,0,.15);z-index:9999;display:flex;flex-direction:column;overflow:hidden;transition:all .3s;font-family:'Plus Jakarta Sans',system-ui,sans-serif}
|
| 313 |
-
.ai-hidden{transform:translateY(20px);opacity:0;pointer-events:none}
|
| 314 |
-
.ai-header{background:linear-gradient(135deg,#0f172a,#1e293b);color:white;padding:14px 18px;display:flex;align-items:center;justify-content:space-between}
|
| 315 |
-
.ai-header-left{display:flex;align-items:center;gap:10px}
|
| 316 |
-
.ai-logo{width:36px;height:36px;background:linear-gradient(135deg,#00d4ff,#00d4ff);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:16px;color:#0f172a}
|
| 317 |
-
.ai-header-info h3{margin:0;font-size:16px;font-weight:700}
|
| 318 |
-
.ai-status{display:flex;align-items:center;gap:5px;font-size:11px;opacity:.8}
|
| 319 |
-
.ai-status-dot{width:6px;height:6px;background:#22c55e;border-radius:50%}
|
| 320 |
-
.ai-header-actions{display:flex;gap:6px}
|
| 321 |
-
.ai-header-btn{width:28px;height:28px;border-radius:6px;border:none;background:rgba(255,255,255,.1);color:white;cursor:pointer;font-size:12px}
|
| 322 |
-
.ai-header-btn:hover{background:rgba(255,255,255,.2)}
|
| 323 |
-
.ai-messages{flex:1;overflow-y:auto;padding:16px;background:#f8fafc}
|
| 324 |
-
.ai-message{display:flex;gap:10px;margin-bottom:14px;animation:fadeIn .3s}
|
| 325 |
-
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
|
| 326 |
-
.ai-message-user{flex-direction:row-reverse}
|
| 327 |
-
.ai-avatar{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:13px}
|
| 328 |
-
.ai-avatar-assistant{background:linear-gradient(135deg,#00d4ff,#00d4ff);color:#0f172a}
|
| 329 |
-
.ai-avatar-user{background:#0f172a;color:#00d4ff}
|
| 330 |
-
.ai-bubble{max-width:260px;padding:10px 14px;border-radius:14px;font-size:13px;line-height:1.5}
|
| 331 |
-
.ai-bubble-assistant{background:white;color:#1e293b;border-bottom-left-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.05)}
|
| 332 |
-
.ai-bubble-user{background:linear-gradient(135deg,#00d4ff,#00d4ff);color:#0f172a;border-bottom-right-radius:4px}
|
| 333 |
-
.ai-bubble strong{font-weight:600}
|
| 334 |
-
.ai-message-image{margin-bottom:8px}
|
| 335 |
-
.ai-message-image img{max-width:200px;max-height:150px;border-radius:8px;object-fit:cover;box-shadow:0 2px 8px rgba(0,0,0,.1)}
|
| 336 |
-
/* Product card / action-result / button styles live in #ai-shared-styles (injected once at top of file) so they're shared with full-page mode */
|
| 337 |
-
.ai-quick-actions{padding:10px 16px;border-top:1px solid #e2e8f0;background:white}
|
| 338 |
-
.ai-quick-title{font-size:11px;color:#64748b;margin:0 0 6px 0}
|
| 339 |
-
.ai-categories{display:flex;gap:6px;flex-wrap:wrap}
|
| 340 |
-
.ai-category-btn{display:flex;align-items:center;gap:4px;padding:5px 10px;border-radius:16px;border:1px solid #e2e8f0;background:white;font-size:11px;cursor:pointer;color:#475569}
|
| 341 |
-
.ai-category-btn:hover{border-color:#00d4ff;background:#fffbeb;color:#b45309}
|
| 342 |
-
.ai-image-preview{padding:10px 16px;background:#f8fafc;border-top:1px solid #e2e8f0}
|
| 343 |
-
.ai-preview-container{position:relative;display:inline-block}
|
| 344 |
-
.ai-preview-container img{max-width:80px;max-height:80px;border-radius:6px;object-fit:cover}
|
| 345 |
-
.ai-remove-image{position:absolute;top:-6px;right:-6px;width:20px;height:20px;border-radius:50%;border:none;background:#ef4444;color:white;cursor:pointer;font-size:10px}
|
| 346 |
-
.ai-input-area{padding:10px 16px;background:white;border-top:1px solid #e2e8f0}
|
| 347 |
-
.ai-input-container{display:flex;gap:6px;align-items:center;background:#f1f5f9;border-radius:20px;padding:4px}
|
| 348 |
-
.ai-upload-btn{width:36px;height:36px;border-radius:50%;border:none;background:transparent;color:#64748b;cursor:pointer}
|
| 349 |
-
.ai-upload-btn:hover{background:#e2e8f0;color:#00d4ff}
|
| 350 |
-
#ai-input{flex:1;border:none;background:transparent;padding:6px;font-size:13px;outline:none}
|
| 351 |
-
.ai-send-btn{width:36px;height:36px;border-radius:50%;border:none;background:linear-gradient(135deg,#00d4ff,#00d4ff);color:#0f172a;cursor:pointer}
|
| 352 |
-
.ai-send-btn:hover{transform:scale(1.05)}
|
| 353 |
-
.ai-loading{display:flex;gap:4px;padding:12px}
|
| 354 |
-
.ai-loading span{width:6px;height:6px;background:#00d4ff;border-radius:50%;animation:bounce 1.4s infinite}
|
| 355 |
-
.ai-loading span:nth-child(1){animation-delay:-.32s}
|
| 356 |
-
.ai-loading span:nth-child(2){animation-delay:-.16s}
|
| 357 |
-
@keyframes bounce{0%,80%,100%{transform:scale(0)}40%{transform:scale(1)}}
|
| 358 |
-
@media(max-width:480px){.ai-widget{bottom:0;right:0;width:100%;height:100%;max-height:100%;border-radius:0}.ai-toggle-btn{bottom:80px}}
|
| 359 |
-
`;
|
| 360 |
-
}
|
| 361 |
-
|
| 362 |
-
function toggleWidget() {
|
| 363 |
-
isOpen = !isOpen;
|
| 364 |
-
const widget = document.getElementById('ai-widget');
|
| 365 |
-
const toggleBtn = document.getElementById('ai-toggle-btn');
|
| 366 |
-
|
| 367 |
-
if (isOpen) {
|
| 368 |
-
widget.classList.remove('ai-hidden');
|
| 369 |
-
toggleBtn.style.display = 'none';
|
| 370 |
-
document.getElementById('ai-input').focus();
|
| 371 |
-
if (conversationHistory.length === 0) showWelcomeMessage();
|
| 372 |
-
} else {
|
| 373 |
-
widget.classList.add('ai-hidden');
|
| 374 |
-
toggleBtn.style.display = 'flex';
|
| 375 |
-
}
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
function showWelcomeMessage() {
|
| 379 |
-
if (isFullPage) return; // Welcome already in HTML
|
| 380 |
-
addMessage('assistant', `<strong>Hi! I'm Cellex, your AI shopping assistant!</strong><br><br>
|
| 381 |
-
I can help you with:<br>
|
| 382 |
-
- Finding products within your budget<br>
|
| 383 |
-
- Planning your shopping list<br>
|
| 384 |
-
- Answering any questions<br><br>
|
| 385 |
-
Just talk to me naturally!<br>
|
| 386 |
-
<strong>100% FREE & Open Source!</strong>`);
|
| 387 |
-
}
|
| 388 |
-
|
| 389 |
-
function addMessage(role, content, data = {}) {
|
| 390 |
-
if (isFullPage) {
|
| 391 |
-
fullPage_addMessage(role, content, data);
|
| 392 |
-
return;
|
| 393 |
-
}
|
| 394 |
-
|
| 395 |
-
const messagesContainer = document.getElementById('ai-messages');
|
| 396 |
-
const isUser = role === 'user';
|
| 397 |
-
const parsed = content.replace(/\*\*(.*?)\*\*/g, '<strong>₦1</strong>').replace(/\n/g, '<br>');
|
| 398 |
-
|
| 399 |
-
let imageHtml = '';
|
| 400 |
-
if (data.image) {
|
| 401 |
-
imageHtml = `<div class="ai-message-image"><img src="${data.image}" alt="Shared image"></div>`;
|
| 402 |
-
}
|
| 403 |
-
|
| 404 |
-
let html = `
|
| 405 |
-
<div class="ai-message ${isUser ? 'ai-message-user' : ''}">
|
| 406 |
-
<div class="ai-avatar ${isUser ? 'ai-avatar-user' : 'ai-avatar-assistant'}">
|
| 407 |
-
<i class="fas ${isUser ? 'fa-user' : 'fa-wand-magic-sparkles'}"></i>
|
| 408 |
-
</div>
|
| 409 |
-
<div class="ai-message-content">
|
| 410 |
-
${imageHtml}
|
| 411 |
-
<div class="ai-bubble ${isUser ? 'ai-bubble-user' : 'ai-bubble-assistant'}">${parsed}</div>
|
| 412 |
-
${renderProductsWidget(data.products)}
|
| 413 |
-
${renderActionResultWidget(data.actionResult)}
|
| 414 |
-
</div>
|
| 415 |
-
</div>`;
|
| 416 |
-
|
| 417 |
-
messagesContainer.insertAdjacentHTML('beforeend', html);
|
| 418 |
-
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| 419 |
-
addProductHandlers();
|
| 420 |
-
|
| 421 |
-
if (conversationHistory.length > 0 || role === 'user') {
|
| 422 |
-
document.getElementById('ai-quick-actions').style.display = 'none';
|
| 423 |
-
}
|
| 424 |
-
}
|
| 425 |
-
|
| 426 |
-
function renderProductsWidget(products) {
|
| 427 |
-
if (!products || products.length === 0) return '';
|
| 428 |
-
return products.slice(0, 5).map((p, i) => `
|
| 429 |
-
<div class="ai-product-card" data-product-id="${p.id}" data-index="${i + 1}">
|
| 430 |
-
<img src="${p.image_url || 'https://via.placeholder.com/64'}" class="ai-product-img" alt="${p.name || 'Product'}">
|
| 431 |
-
<div class="ai-product-info">
|
| 432 |
-
<div class="ai-product-name">${i + 1}. ${p.name}</div>
|
| 433 |
-
<div class="ai-product-price">₦${(p.price || 0).toLocaleString()}</div>
|
| 434 |
-
<div class="ai-product-meta">${p.category || ''}</div>
|
| 435 |
-
<div class="ai-product-actions">
|
| 436 |
-
<button class="ai-action-btn ai-action-btn-primary" data-action="add" data-product-id="${p.id}"><i class="fas fa-cart-plus"></i> Add</button>
|
| 437 |
-
<button class="ai-action-btn ai-action-btn-secondary" data-action="view" data-product-id="${p.id}"><i class="fas fa-eye"></i> View</button>
|
| 438 |
-
</div>
|
| 439 |
-
</div>
|
| 440 |
-
</div>
|
| 441 |
-
`).join('');
|
| 442 |
-
}
|
| 443 |
-
|
| 444 |
-
function renderActionResultWidget(result) {
|
| 445 |
-
if (!result) return '';
|
| 446 |
-
const cls = result.success ? '' : (result.requiresAuth ? 'warning' : 'error');
|
| 447 |
-
const icon = result.success ? 'fa-check-circle' : (result.requiresAuth ? 'fa-exclamation-triangle' : 'fa-times-circle');
|
| 448 |
-
return `<div class="ai-action-result ${cls}"><i class="fas ${icon}"></i> ${result.message}</div>`;
|
| 449 |
-
}
|
| 450 |
-
|
| 451 |
-
function addProductHandlers() {
|
| 452 |
-
const parentEl = isFullPage ? document.getElementById('chatContainer') : document.getElementById('ai-messages');
|
| 453 |
-
if (!parentEl) return;
|
| 454 |
-
|
| 455 |
-
parentEl.querySelectorAll('.ai-product-card:not([data-handled])').forEach(card => {
|
| 456 |
-
card.setAttribute('data-handled', 'true');
|
| 457 |
-
card.addEventListener('click', e => {
|
| 458 |
-
if (!e.target.closest('.ai-action-btn')) {
|
| 459 |
-
window.location.href = `product.html?id=${card.dataset.productId}`;
|
| 460 |
-
}
|
| 461 |
-
});
|
| 462 |
-
});
|
| 463 |
-
|
| 464 |
-
parentEl.querySelectorAll('.ai-action-btn:not([data-handled])').forEach(btn => {
|
| 465 |
-
btn.setAttribute('data-handled', 'true');
|
| 466 |
-
btn.addEventListener('click', async e => {
|
| 467 |
-
e.stopPropagation();
|
| 468 |
-
const action = btn.dataset.action;
|
| 469 |
-
const productId = parseInt(btn.dataset.productId);
|
| 470 |
-
|
| 471 |
-
if (action === 'add') {
|
| 472 |
-
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
| 473 |
-
const result = await window.Cart?.addToCart(productId, 1);
|
| 474 |
-
|
| 475 |
-
if (result?.success) {
|
| 476 |
-
btn.innerHTML = '<i class="fas fa-check"></i> Added';
|
| 477 |
-
btn.classList.remove('ai-action-btn-primary');
|
| 478 |
-
btn.classList.add('ai-action-btn-secondary');
|
| 479 |
-
} else {
|
| 480 |
-
btn.innerHTML = '<i class="fas fa-cart-plus"></i> Add';
|
| 481 |
-
if (result?.requiresAuth) {
|
| 482 |
-
addMessage('assistant', 'Please <a href="login.html" style="color:#00d4ff;font-weight:600;">login</a> to add items.');
|
| 483 |
-
}
|
| 484 |
-
}
|
| 485 |
-
} else if (action === 'view') {
|
| 486 |
-
window.location.href = `product.html?id=${productId}`;
|
| 487 |
-
}
|
| 488 |
-
});
|
| 489 |
-
});
|
| 490 |
-
}
|
| 491 |
-
|
| 492 |
-
function addLoading() {
|
| 493 |
-
if (isFullPage) {
|
| 494 |
-
fullPage_showTypingIndicator();
|
| 495 |
-
return;
|
| 496 |
-
}
|
| 497 |
-
const c = document.getElementById('ai-messages');
|
| 498 |
-
c.insertAdjacentHTML('beforeend', `<div class="ai-message" id="ai-loading-msg">
|
| 499 |
-
<div class="ai-avatar ai-avatar-assistant"><i class="fas fa-wand-magic-sparkles"></i></div>
|
| 500 |
-
<div class="ai-bubble ai-bubble-assistant"><div class="ai-loading"><span></span><span></span><span></span></div></div>
|
| 501 |
-
</div>`);
|
| 502 |
-
c.scrollTop = c.scrollHeight;
|
| 503 |
-
}
|
| 504 |
-
|
| 505 |
-
function removeLoading() {
|
| 506 |
-
if (isFullPage) {
|
| 507 |
-
fullPage_removeTypingIndicator();
|
| 508 |
-
return;
|
| 509 |
-
}
|
| 510 |
-
document.getElementById('ai-loading-msg')?.remove();
|
| 511 |
-
}
|
| 512 |
-
|
| 513 |
-
function handleFileSelect(e) {
|
| 514 |
-
const file = e.target.files[0];
|
| 515 |
-
console.log('[Cellex AI] File selected:', file?.name, file?.type, file?.size);
|
| 516 |
-
if (!file?.type.startsWith('image/')) return;
|
| 517 |
-
|
| 518 |
-
// Compress image to max 512px before sending (phone photos are too large for HF Space)
|
| 519 |
-
const img = new Image();
|
| 520 |
-
img.onload = function() {
|
| 521 |
-
const MAX = 512;
|
| 522 |
-
let w = img.width, h = img.height;
|
| 523 |
-
if (w > MAX || h > MAX) {
|
| 524 |
-
if (w > h) { h = Math.round(h * MAX / w); w = MAX; }
|
| 525 |
-
else { w = Math.round(w * MAX / h); h = MAX; }
|
| 526 |
-
}
|
| 527 |
-
const canvas = document.createElement('canvas');
|
| 528 |
-
canvas.width = w; canvas.height = h;
|
| 529 |
-
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
| 530 |
-
selectedImage = canvas.toDataURL('image/jpeg', 0.7);
|
| 531 |
-
console.log('[Cellex AI] Image compressed. Original:', file.size, 'bytes. Base64 length:', selectedImage.length);
|
| 532 |
-
|
| 533 |
-
if (isFullPage) {
|
| 534 |
-
const preview = document.getElementById('ai-image-preview');
|
| 535 |
-
if (preview) {
|
| 536 |
-
document.getElementById('ai-preview-img').src = selectedImage;
|
| 537 |
-
preview.classList.remove('hidden');
|
| 538 |
-
}
|
| 539 |
-
} else {
|
| 540 |
-
document.getElementById('ai-preview-img').src = selectedImage;
|
| 541 |
-
document.getElementById('ai-image-preview').classList.remove('ai-hidden');
|
| 542 |
-
}
|
| 543 |
-
};
|
| 544 |
-
img.onerror = function() {
|
| 545 |
-
// Fallback: use original file
|
| 546 |
-
const reader = new FileReader();
|
| 547 |
-
reader.onload = ev => {
|
| 548 |
-
selectedImage = ev.target.result;
|
| 549 |
-
if (isFullPage) {
|
| 550 |
-
const preview = document.getElementById('ai-image-preview');
|
| 551 |
-
if (preview) {
|
| 552 |
-
document.getElementById('ai-preview-img').src = selectedImage;
|
| 553 |
-
preview.classList.remove('hidden');
|
| 554 |
-
}
|
| 555 |
-
} else {
|
| 556 |
-
document.getElementById('ai-preview-img').src = selectedImage;
|
| 557 |
-
document.getElementById('ai-image-preview').classList.remove('ai-hidden');
|
| 558 |
-
}
|
| 559 |
-
};
|
| 560 |
-
reader.readAsDataURL(file);
|
| 561 |
-
};
|
| 562 |
-
img.src = URL.createObjectURL(file);
|
| 563 |
-
e.target.value = '';
|
| 564 |
-
}
|
| 565 |
-
|
| 566 |
-
function removeSelectedImage() {
|
| 567 |
-
selectedImage = null;
|
| 568 |
-
if (isFullPage) {
|
| 569 |
-
const preview = document.getElementById('ai-image-preview');
|
| 570 |
-
if (preview) preview.classList.add('hidden');
|
| 571 |
-
} else {
|
| 572 |
-
document.getElementById('ai-image-preview').classList.add('ai-hidden');
|
| 573 |
-
}
|
| 574 |
-
}
|
| 575 |
-
|
| 576 |
-
function handleKeyPress(e) {
|
| 577 |
-
if (e.key === 'Enter' && !e.shiftKey) {
|
| 578 |
-
e.preventDefault();
|
| 579 |
-
sendMessage();
|
| 580 |
-
}
|
| 581 |
-
}
|
| 582 |
-
|
| 583 |
-
// ============================================
|
| 584 |
-
// SHARED LOGIC — used by BOTH modes
|
| 585 |
-
// ============================================
|
| 586 |
-
|
| 587 |
-
async function updateContext() {
|
| 588 |
-
try {
|
| 589 |
-
console.log('[Cellex AI] Checking login status...');
|
| 590 |
-
console.log('[Cellex AI] window.Cart exists:', !!window.Cart);
|
| 591 |
-
console.log('[Cellex AI] window.EdgeFunctions exists:', !!window.EdgeFunctions);
|
| 592 |
-
|
| 593 |
-
if (!window.Cart) {
|
| 594 |
-
console.error('[Cellex AI] Cart module not loaded!');
|
| 595 |
-
await new Promise(resolve => setTimeout(resolve, 500));
|
| 596 |
-
if (!window.Cart) {
|
| 597 |
-
console.error('[Cellex AI] Cart module still not loaded after waiting');
|
| 598 |
-
context.user = null;
|
| 599 |
-
context.cartItems = [];
|
| 600 |
-
return;
|
| 601 |
-
}
|
| 602 |
-
}
|
| 603 |
-
|
| 604 |
-
context.user = await window.Cart.getCurrentUser();
|
| 605 |
-
console.log('[Cellex AI] User object:', context.user ? `ID: ${context.user.id}` : 'null');
|
| 606 |
-
|
| 607 |
-
if (context.user) {
|
| 608 |
-
console.log('[Cellex AI] Fetching cart items for user...');
|
| 609 |
-
const items = await window.Cart.getCartItems();
|
| 610 |
-
context.cartItems = items || [];
|
| 611 |
-
console.log('[Cellex AI] Cart items fetched:', context.cartItems.length);
|
| 612 |
-
} else {
|
| 613 |
-
context.cartItems = [];
|
| 614 |
-
console.log('[Cellex AI] No user logged in - cart empty');
|
| 615 |
-
}
|
| 616 |
-
} catch (e) {
|
| 617 |
-
console.error('[Cellex AI] Error updating context:', e);
|
| 618 |
-
console.error('[Cellex AI] Error stack:', e.stack);
|
| 619 |
-
}
|
| 620 |
-
}
|
| 621 |
-
|
| 622 |
-
async function sendMessage() {
|
| 623 |
-
const input = isFullPage ? document.getElementById('chatInput') : document.getElementById('ai-input');
|
| 624 |
-
const text = input.value.trim();
|
| 625 |
-
if ((!text && !selectedImage) || isLoading) return;
|
| 626 |
-
|
| 627 |
-
// DEBUG COMMAND
|
| 628 |
-
if (text && (text.toLowerCase() === 'debug' || text.toLowerCase() === '/debug')) {
|
| 629 |
-
addMessage('user', text);
|
| 630 |
-
input.value = '';
|
| 631 |
-
|
| 632 |
-
let debugInfo = '**DEBUG INFO:**\n\n';
|
| 633 |
-
debugInfo += `window.Cart: ${window.Cart ? 'Loaded' : 'NOT LOADED'}\n`;
|
| 634 |
-
debugInfo += `window.EdgeFunctions: ${window.EdgeFunctions ? 'Loaded' : 'NOT LOADED'}\n`;
|
| 635 |
-
|
| 636 |
-
if (window.Cart) {
|
| 637 |
-
try {
|
| 638 |
-
const user = await window.Cart.getCurrentUser();
|
| 639 |
-
debugInfo += `\n**Login Status:**\n`;
|
| 640 |
-
debugInfo += `Logged in: ${user ? 'YES' : 'NO'}\n`;
|
| 641 |
-
if (user) debugInfo += `User ID: ${user.id}\n`;
|
| 642 |
-
|
| 643 |
-
const items = await window.Cart.getCartItems();
|
| 644 |
-
debugInfo += `\n**Cart Data:**\n`;
|
| 645 |
-
debugInfo += `Items count: ${items ? items.length : 0}\n`;
|
| 646 |
-
if (items && items.length > 0) {
|
| 647 |
-
debugInfo += `Items:\n`;
|
| 648 |
-
items.forEach((item, i) => {
|
| 649 |
-
debugInfo += ` ${i+1}. ${item.products?.name || 'Unknown'} x${item.quantity}\n`;
|
| 650 |
-
});
|
| 651 |
-
}
|
| 652 |
-
} catch (e) {
|
| 653 |
-
debugInfo += `\nError: ${e.message}\n`;
|
| 654 |
-
}
|
| 655 |
-
}
|
| 656 |
-
|
| 657 |
-
addMessage('assistant', debugInfo);
|
| 658 |
-
return;
|
| 659 |
-
}
|
| 660 |
-
|
| 661 |
-
const imageToSend = selectedImage;
|
| 662 |
-
const displayText = text || 'Shared an image';
|
| 663 |
-
console.log('[Cellex AI] sendMessage called. Has image:', !!imageToSend, 'Text:', displayText);
|
| 664 |
-
addMessage('user', displayText, { image: imageToSend });
|
| 665 |
-
input.value = '';
|
| 666 |
-
removeSelectedImage();
|
| 667 |
-
|
| 668 |
-
isLoading = true;
|
| 669 |
-
addLoading();
|
| 670 |
-
|
| 671 |
-
try {
|
| 672 |
-
await updateContext();
|
| 673 |
-
|
| 674 |
-
const contextForAI = {
|
| 675 |
-
lastShownProducts: context.lastShownProducts.map(p => ({
|
| 676 |
-
id: p.id, name: p.name, price: p.price, category: p.category
|
| 677 |
-
})),
|
| 678 |
-
cartItems: context.cartItems.map(i => ({
|
| 679 |
-
product_name: i.products?.name || i.product_name || 'Unknown',
|
| 680 |
-
quantity: i.quantity || 1,
|
| 681 |
-
price: i.products?.price || i.price || 0
|
| 682 |
-
})),
|
| 683 |
-
cartTotal: context.cartItems.reduce((s, i) => s + ((i.products?.price || i.price || 0) * (i.quantity || 1)), 0),
|
| 684 |
-
isLoggedIn: !!context.user,
|
| 685 |
-
conversationHistory: conversationHistory.slice(-10)
|
| 686 |
-
};
|
| 687 |
-
|
| 688 |
-
if (imageToSend) {
|
| 689 |
-
contextForAI.image = imageToSend;
|
| 690 |
-
console.log('[Cellex AI] Image included in request. Base64 length:', imageToSend.length);
|
| 691 |
-
}
|
| 692 |
-
|
| 693 |
-
// Update typing status after 10s
|
| 694 |
-
let statusTimer;
|
| 695 |
-
if (isFullPage) {
|
| 696 |
-
statusTimer = setTimeout(() => {
|
| 697 |
-
fullPage_updateTypingText('Thinking hard, please wait...');
|
| 698 |
-
}, 10000);
|
| 699 |
-
}
|
| 700 |
-
|
| 701 |
-
// Build message text — tell AI this is a product they should search for
|
| 702 |
-
const messageForAI = imageToSend
|
| 703 |
-
? `[User sent a product image] ${text || 'Find me this product or similar ones'}`
|
| 704 |
-
: text;
|
| 705 |
-
|
| 706 |
-
// Call AI via Supabase Edge Function (server-side).
|
| 707 |
-
// The edge function handles: AI model call, Supabase product search,
|
| 708 |
-
// cart context fetching, login enforcement. Frontend just gets the result.
|
| 709 |
-
const data = await window.EdgeFunctions.aiChat(messageForAI, contextForAI);
|
| 710 |
-
|
| 711 |
-
if (statusTimer) clearTimeout(statusTimer);
|
| 712 |
-
|
| 713 |
-
if (CONFIG.debug) console.log('[Cellex AI] Response:', data);
|
| 714 |
-
|
| 715 |
-
removeLoading();
|
| 716 |
-
|
| 717 |
-
if (data.success) {
|
| 718 |
-
conversationHistory.push({ role: 'user', content: text });
|
| 719 |
-
conversationHistory.push({ role: 'assistant', content: data.response });
|
| 720 |
-
|
| 721 |
-
if (conversationHistory.length > 20) {
|
| 722 |
-
conversationHistory = conversationHistory.slice(-20);
|
| 723 |
-
}
|
| 724 |
-
|
| 725 |
-
if (data.products?.length > 0) {
|
| 726 |
-
context.lastShownProducts = data.products;
|
| 727 |
-
}
|
| 728 |
-
|
| 729 |
-
let actionResult = null;
|
| 730 |
-
let actionProducts = null;
|
| 731 |
-
if (data.action) {
|
| 732 |
-
actionResult = await executeAction(data.action, data);
|
| 733 |
-
// If the action returned products (e.g. view_cart), include them for rendering
|
| 734 |
-
if (actionResult?.products?.length > 0) {
|
| 735 |
-
actionProducts = actionResult.products;
|
| 736 |
-
}
|
| 737 |
-
}
|
| 738 |
-
|
| 739 |
-
// Merge products from API response and action results
|
| 740 |
-
const allProducts = data.products?.length > 0 ? data.products : actionProducts;
|
| 741 |
-
|
| 742 |
-
// Show image debug info so user can see on phone (no laptop needed)
|
| 743 |
-
let debugTag = '';
|
| 744 |
-
if (imageToSend) {
|
| 745 |
-
const imgDesc = data.image_description || 'not processed';
|
| 746 |
-
debugTag = `\n\n🖼️ Image sent (${Math.round(imageToSend.length / 1024)}KB) → AI saw: "${imgDesc}"`;
|
| 747 |
-
}
|
| 748 |
-
addMessage('assistant', data.response + debugTag, { products: allProducts, actionResult });
|
| 749 |
-
} else {
|
| 750 |
-
addMessage('assistant', data.response || 'Sorry, an error occurred.');
|
| 751 |
-
}
|
| 752 |
-
} catch (error) {
|
| 753 |
-
console.error('[Cellex AI] Error:', error);
|
| 754 |
-
removeLoading();
|
| 755 |
-
|
| 756 |
-
let errorMsg = "Connection error. Please try again.";
|
| 757 |
-
if (error.name === 'AbortError') {
|
| 758 |
-
errorMsg = "The AI assistant took too long to respond. The server might be busy — please try again.";
|
| 759 |
-
}
|
| 760 |
-
addMessage('assistant', errorMsg);
|
| 761 |
-
}
|
| 762 |
-
|
| 763 |
-
isLoading = false;
|
| 764 |
-
}
|
| 765 |
-
|
| 766 |
-
async function executeAction(action, data) {
|
| 767 |
-
const Cart = window.Cart;
|
| 768 |
-
if (!Cart) return { success: false, message: 'Cart unavailable' };
|
| 769 |
-
|
| 770 |
-
const user = await Cart.getCurrentUser();
|
| 771 |
-
if (!user) return { success: false, requiresAuth: true, message: 'Please login first.' };
|
| 772 |
-
|
| 773 |
-
const type = action.type;
|
| 774 |
-
|
| 775 |
-
if (type === 'add_to_cart') {
|
| 776 |
-
let toAdd = [];
|
| 777 |
-
const qty = action.quantity || 1;
|
| 778 |
-
|
| 779 |
-
if (action.product_id) {
|
| 780 |
-
const p = context.lastShownProducts.find(p => p.id === action.product_id);
|
| 781 |
-
if (p) toAdd = [p];
|
| 782 |
-
} else if (action.all) {
|
| 783 |
-
toAdd = context.lastShownProducts;
|
| 784 |
-
} else if (action.product_index !== undefined) {
|
| 785 |
-
const p = context.lastShownProducts[action.product_index - 1];
|
| 786 |
-
if (p) toAdd = [p];
|
| 787 |
-
} else if (action.productIndex !== undefined) {
|
| 788 |
-
const p = context.lastShownProducts[action.productIndex - 1];
|
| 789 |
-
if (p) toAdd = [p];
|
| 790 |
-
} else if (action.productIds) {
|
| 791 |
-
toAdd = context.lastShownProducts.filter(p => action.productIds.includes(p.id));
|
| 792 |
-
} else if (context.lastShownProducts.length > 0) {
|
| 793 |
-
toAdd = context.lastShownProducts.slice(0, 1);
|
| 794 |
-
}
|
| 795 |
-
|
| 796 |
-
if (toAdd.length === 0) {
|
| 797 |
-
return { success: false, message: 'Which product would you like to add?' };
|
| 798 |
-
}
|
| 799 |
-
|
| 800 |
-
let added = 0, total = 0;
|
| 801 |
-
for (const p of toAdd) {
|
| 802 |
-
const r = await Cart.addToCart(p.id, qty);
|
| 803 |
-
if (r.success) { added++; total += (p.price || 0) * qty; }
|
| 804 |
-
}
|
| 805 |
-
|
| 806 |
-
return {
|
| 807 |
-
success: true,
|
| 808 |
-
message: `Added ${added} item(s) to cart${qty > 1 ? ` (${qty}x each)` : ''} - Total: ₦${total.toLocaleString()}`
|
| 809 |
-
};
|
| 810 |
-
}
|
| 811 |
-
|
| 812 |
-
if (type === 'remove_from_cart') {
|
| 813 |
-
// HF Space sends product_index (number from cart list), also supports product_id
|
| 814 |
-
if (action.product_index !== undefined || action.cart_item_number !== undefined) {
|
| 815 |
-
const items = await Cart.getCartItems();
|
| 816 |
-
const idx = action.product_index ?? action.cart_item_number;
|
| 817 |
-
if (items && items.length >= idx && idx >= 1) {
|
| 818 |
-
const item = items[idx - 1];
|
| 819 |
-
const productId = item.product_id || item.products?.id;
|
| 820 |
-
if (productId) {
|
| 821 |
-
const result = await Cart.removeFromCart(productId);
|
| 822 |
-
if (result.success) {
|
| 823 |
-
return { success: true, message: 'Item removed from cart!' };
|
| 824 |
-
}
|
| 825 |
-
}
|
| 826 |
-
}
|
| 827 |
-
return { success: false, message: 'Could not find that item in cart.' };
|
| 828 |
-
}
|
| 829 |
-
if (action.product_id) {
|
| 830 |
-
const result = await Cart.removeFromCart(action.product_id);
|
| 831 |
-
if (result.success) {
|
| 832 |
-
return { success: true, message: 'Item removed from cart!' };
|
| 833 |
-
}
|
| 834 |
-
return { success: false, message: 'Could not remove item.' };
|
| 835 |
-
}
|
| 836 |
-
return { success: false, message: 'Which item would you like to remove?' };
|
| 837 |
-
}
|
| 838 |
-
|
| 839 |
-
if (type === 'view_cart') {
|
| 840 |
-
const items = await Cart.getCartItems();
|
| 841 |
-
|
| 842 |
-
if (!items || !items.length) {
|
| 843 |
-
const user = await Cart.getCurrentUser();
|
| 844 |
-
if (!user) {
|
| 845 |
-
return { success: false, requiresAuth: true, message: 'Please login to view your cart.' };
|
| 846 |
-
}
|
| 847 |
-
return { success: true, message: 'Your cart is empty. Would you like to browse some products?' };
|
| 848 |
-
}
|
| 849 |
-
|
| 850 |
-
let cartDetails = 'Your Cart:\n';
|
| 851 |
-
let total = 0;
|
| 852 |
-
// Build products array with images for rendering
|
| 853 |
-
const cartProducts = items.map((item, i) => {
|
| 854 |
-
const name = item.products?.name || 'Unknown';
|
| 855 |
-
const price = item.products?.price || 0;
|
| 856 |
-
const qty = item.quantity || 1;
|
| 857 |
-
const subtotal = price * qty;
|
| 858 |
-
total += subtotal;
|
| 859 |
-
cartDetails += `${i+1}. ${name} x${qty} - ₦${subtotal.toLocaleString()}\n`;
|
| 860 |
-
return {
|
| 861 |
-
id: item.product_id || item.products?.id,
|
| 862 |
-
name: name,
|
| 863 |
-
price: price,
|
| 864 |
-
image_url: item.products?.image_url || null,
|
| 865 |
-
category: item.products?.category || ''
|
| 866 |
-
};
|
| 867 |
-
});
|
| 868 |
-
cartDetails += `\nTotal: ₦${total.toLocaleString()}`;
|
| 869 |
-
|
| 870 |
-
// Store cart products so they render with images
|
| 871 |
-
context.lastShownProducts = cartProducts;
|
| 872 |
-
|
| 873 |
-
return { success: true, message: cartDetails, products: cartProducts };
|
| 874 |
-
}
|
| 875 |
-
|
| 876 |
-
if (type === 'checkout') {
|
| 877 |
-
window.location.href = 'checkout.html';
|
| 878 |
-
return { success: true, message: 'Redirecting to checkout...' };
|
| 879 |
-
}
|
| 880 |
-
|
| 881 |
-
if (type === 'clear_cart') {
|
| 882 |
-
const result = await Cart.clearCart();
|
| 883 |
-
if (result.success) {
|
| 884 |
-
context.lastShownProducts = [];
|
| 885 |
-
return { success: true, message: 'Your cart has been cleared! It is now empty.' };
|
| 886 |
-
}
|
| 887 |
-
return { success: false, message: 'Could not clear cart. Please try again.' };
|
| 888 |
-
}
|
| 889 |
-
|
| 890 |
-
if (type === 'update_cart') {
|
| 891 |
-
const cartItems = await Cart.getCartItems();
|
| 892 |
-
const cartItemNumber = action.cart_item_number || 1;
|
| 893 |
-
const newQuantity = action.new_quantity;
|
| 894 |
-
|
| 895 |
-
if (cartItems && cartItems.length >= cartItemNumber && cartItemNumber >= 1 && newQuantity !== undefined) {
|
| 896 |
-
const item = cartItems[cartItemNumber - 1];
|
| 897 |
-
const result = await Cart.updateQuantity(item.id, newQuantity);
|
| 898 |
-
if (result.success) {
|
| 899 |
-
return { success: true, message: newQuantity <= 0 ? 'Item removed from cart!' : `Quantity updated to ${newQuantity}!` };
|
| 900 |
-
}
|
| 901 |
-
return { success: false, message: 'Could not update quantity.' };
|
| 902 |
-
}
|
| 903 |
-
return { success: false, message: cartItems && cartItems.length > 0 ? `Choose a number between 1 and ${cartItems.length}` : 'Your cart is empty.' };
|
| 904 |
-
}
|
| 905 |
-
|
| 906 |
-
return null;
|
| 907 |
-
}
|
| 908 |
-
|
| 909 |
-
async function clearConversation() {
|
| 910 |
-
conversationHistory = [];
|
| 911 |
-
context.lastShownProducts = [];
|
| 912 |
-
|
| 913 |
-
if (isFullPage) {
|
| 914 |
-
const chatContainer = document.getElementById('chatContainer');
|
| 915 |
-
chatContainer.innerHTML = '';
|
| 916 |
-
|
| 917 |
-
// Re-add welcome message
|
| 918 |
-
const welcomeDiv = document.createElement('div');
|
| 919 |
-
welcomeDiv.className = 'flex items-start gap-2.5 mb-4 mt-4 animate-fade-in';
|
| 920 |
-
welcomeDiv.innerHTML = `
|
| 921 |
-
<div class="w-8 h-8 bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 shadow-sm">
|
| 922 |
-
<i class="fas fa-robot text-white text-xs"></i>
|
| 923 |
-
</div>
|
| 924 |
-
<div class="message-bubble bg-white rounded-2xl rounded-tl-md p-3.5 shadow-sm border border-gray-100">
|
| 925 |
-
<p class="text-sm text-gray-700 leading-relaxed">Hi! I'm your <strong class="text-cyan-500">Cellex AI Assistant</strong>. I can help you find products, compare prices, track deals, and more! What are you looking for today?</p>
|
| 926 |
-
<p class="text-[10px] text-gray-400 mt-1.5">Just now</p>
|
| 927 |
-
</div>`;
|
| 928 |
-
chatContainer.appendChild(welcomeDiv);
|
| 929 |
-
|
| 930 |
-
// Re-add suggestion chips
|
| 931 |
-
const chipsDiv = document.createElement('div');
|
| 932 |
-
chipsDiv.id = 'suggestionChips';
|
| 933 |
-
chipsDiv.className = 'flex flex-wrap gap-2 mb-4 ml-10';
|
| 934 |
-
chipsDiv.innerHTML = `
|
| 935 |
-
<button onclick="sendSuggestion('Show me phones under ₦50,000')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 936 |
-
<i class="fas fa-mobile-alt text-orange-400"></i> Phones under ₦50k
|
| 937 |
-
</button>
|
| 938 |
-
<button onclick="sendSuggestion('Best deals today')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 939 |
-
<i class="fas fa-bolt text-orange-400"></i> Best deals today
|
| 940 |
-
</button>
|
| 941 |
-
<button onclick="sendSuggestion('Farm fresh produce')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 942 |
-
<i class="fas fa-leaf text-orange-400"></i> Farm fresh produce
|
| 943 |
-
</button>
|
| 944 |
-
<button onclick="sendSuggestion('Electronics')" class="chip inline-flex items-center gap-1.5 px-3 py-2 bg-white border border-gray-200 rounded-full text-xs text-gray-600 hover:text-cyan-600 shadow-sm">
|
| 945 |
-
<i class="fas fa-laptop text-orange-400"></i> Electronics
|
| 946 |
-
</button>`;
|
| 947 |
-
chatContainer.appendChild(chipsDiv);
|
| 948 |
-
} else {
|
| 949 |
-
document.getElementById('ai-messages').innerHTML = '';
|
| 950 |
-
document.getElementById('ai-quick-actions').style.display = 'block';
|
| 951 |
-
showWelcomeMessage();
|
| 952 |
-
}
|
| 953 |
-
}
|
| 954 |
-
|
| 955 |
-
// Expose globally
|
| 956 |
-
window.CellexAI = {
|
| 957 |
-
open: toggleWidget,
|
| 958 |
-
close: () => { if (isOpen) toggleWidget(); },
|
| 959 |
-
clearHistory: clearConversation
|
| 960 |
-
};
|
| 961 |
-
window.sendMessage = sendMessage;
|
| 962 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/cart.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* Cellex Cart Module (Cookie-Based Auth Edition)
|
| 3 |
-
* ----------------------------------------------
|
| 4 |
-
* All cart operations go through the /api/cart proxy → Supabase Edge Function.
|
| 5 |
-
* No Supabase SDK, no localStorage, no tokens in JS.
|
| 6 |
-
* Auth is handled via HTTP-only cookies set by the server.
|
| 7 |
-
*
|
| 8 |
-
* Usage:
|
| 9 |
-
* <script src="js/config/edge-functions.js"></script>
|
| 10 |
-
* <script src="js/cart.js"></script>
|
| 11 |
-
* window.Cart.addToCart(productId);
|
| 12 |
-
*/
|
| 13 |
-
|
| 14 |
-
(function() {
|
| 15 |
-
'use strict';
|
| 16 |
-
|
| 17 |
-
/**
|
| 18 |
-
* Get current user (async — calls checkSession if needed).
|
| 19 |
-
* Replaces supabase.auth.getSession().
|
| 20 |
-
*/
|
| 21 |
-
async function getCurrentUser() {
|
| 22 |
-
return await window.EdgeFunctions.auth.getCurrentUserAsync();
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
/**
|
| 26 |
-
* Get total cart items count
|
| 27 |
-
*/
|
| 28 |
-
async function getCartCount() {
|
| 29 |
-
const user = await getCurrentUser();
|
| 30 |
-
if (!user) return 0;
|
| 31 |
-
|
| 32 |
-
const result = await window.EdgeFunctions.cart.count();
|
| 33 |
-
if (!result.success) {
|
| 34 |
-
console.error('[Cart] Error fetching cart count:', result.error);
|
| 35 |
-
return 0;
|
| 36 |
-
}
|
| 37 |
-
return result.count || 0;
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
/**
|
| 41 |
-
* Update all cart count elements on the page
|
| 42 |
-
*/
|
| 43 |
-
async function updateCartCountUI() {
|
| 44 |
-
const count = await getCartCount();
|
| 45 |
-
|
| 46 |
-
const cartCountEl = document.getElementById('cartCount');
|
| 47 |
-
const mobileCartCountEl = document.getElementById('mobileCartCount');
|
| 48 |
-
|
| 49 |
-
if (cartCountEl) {
|
| 50 |
-
cartCountEl.textContent = count;
|
| 51 |
-
cartCountEl.style.display = count > 0 ? 'flex' : 'none';
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
if (mobileCartCountEl) {
|
| 55 |
-
mobileCartCountEl.textContent = count;
|
| 56 |
-
mobileCartCountEl.style.display = count > 0 ? 'flex' : 'none';
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
return count;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
/**
|
| 63 |
-
* Get all cart items with product details
|
| 64 |
-
*/
|
| 65 |
-
async function getCartItems() {
|
| 66 |
-
const user = await getCurrentUser();
|
| 67 |
-
if (!user) {
|
| 68 |
-
return [];
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
const result = await window.EdgeFunctions.cart.get();
|
| 72 |
-
if (!result.success) {
|
| 73 |
-
console.error('[Cart] Error fetching cart items:', result.error);
|
| 74 |
-
return [];
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
return result.items || [];
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
/**
|
| 81 |
-
* Add item to cart
|
| 82 |
-
*/
|
| 83 |
-
async function addToCart(productId, quantity = 1) {
|
| 84 |
-
const user = await getCurrentUser();
|
| 85 |
-
if (!user) {
|
| 86 |
-
return {
|
| 87 |
-
success: false,
|
| 88 |
-
requiresAuth: true,
|
| 89 |
-
message: 'Please login to add items to cart'
|
| 90 |
-
};
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
const result = await window.EdgeFunctions.cart.add(productId, quantity);
|
| 94 |
-
|
| 95 |
-
if (result.success) {
|
| 96 |
-
await updateCartCountUI();
|
| 97 |
-
return { success: true, message: 'Added to cart!' };
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
return { success: false, message: result.error || 'Failed to add to cart' };
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
/**
|
| 104 |
-
* Remove item from cart
|
| 105 |
-
*/
|
| 106 |
-
async function removeFromCart(cartItemId) {
|
| 107 |
-
const user = await getCurrentUser();
|
| 108 |
-
if (!user) {
|
| 109 |
-
return { success: false, message: 'Please login' };
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
const result = await window.EdgeFunctions.cart.remove(cartItemId);
|
| 113 |
-
|
| 114 |
-
if (result.success) {
|
| 115 |
-
await updateCartCountUI();
|
| 116 |
-
return { success: true, message: 'Item removed' };
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
return { success: false, message: result.error || 'Failed to remove item' };
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
/**
|
| 123 |
-
* Update item quantity
|
| 124 |
-
*/
|
| 125 |
-
async function updateQuantity(cartItemId, newQuantity) {
|
| 126 |
-
const user = await getCurrentUser();
|
| 127 |
-
if (!user) {
|
| 128 |
-
return { success: false, message: 'Please login' };
|
| 129 |
-
}
|
| 130 |
-
|
| 131 |
-
if (newQuantity < 1) {
|
| 132 |
-
return await removeFromCart(cartItemId);
|
| 133 |
-
}
|
| 134 |
-
|
| 135 |
-
const result = await window.EdgeFunctions.cart.update(cartItemId, newQuantity);
|
| 136 |
-
|
| 137 |
-
if (result.success) {
|
| 138 |
-
await updateCartCountUI();
|
| 139 |
-
return { success: true, message: 'Quantity updated' };
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
return { success: false, message: result.error || 'Failed to update quantity' };
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
/**
|
| 146 |
-
* Clear entire cart
|
| 147 |
-
*/
|
| 148 |
-
async function clearCart() {
|
| 149 |
-
const user = await getCurrentUser();
|
| 150 |
-
if (!user) {
|
| 151 |
-
return { success: false, message: 'Please login' };
|
| 152 |
-
}
|
| 153 |
-
|
| 154 |
-
const result = await window.EdgeFunctions.cart.clear();
|
| 155 |
-
|
| 156 |
-
if (result.success) {
|
| 157 |
-
await updateCartCountUI();
|
| 158 |
-
return { success: true, message: 'Cart cleared' };
|
| 159 |
-
}
|
| 160 |
-
|
| 161 |
-
return { success: false, message: result.error || 'Failed to clear cart' };
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
/**
|
| 165 |
-
* Get cart subtotal
|
| 166 |
-
*/
|
| 167 |
-
async function getCartSubtotal() {
|
| 168 |
-
const cartItems = await getCartItems();
|
| 169 |
-
return cartItems.reduce((sum, item) => {
|
| 170 |
-
const price = item.products?.price || 0;
|
| 171 |
-
return sum + (price * (item.quantity || 0));
|
| 172 |
-
}, 0);
|
| 173 |
-
}
|
| 174 |
-
|
| 175 |
-
/**
|
| 176 |
-
* Initialize cart on page load
|
| 177 |
-
*/
|
| 178 |
-
async function initCart() {
|
| 179 |
-
await updateCartCountUI();
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
// Expose Cart module globally
|
| 183 |
-
window.Cart = {
|
| 184 |
-
getCartCount,
|
| 185 |
-
updateCartCountUI,
|
| 186 |
-
getCartItems,
|
| 187 |
-
addToCart,
|
| 188 |
-
removeFromCart,
|
| 189 |
-
updateQuantity,
|
| 190 |
-
clearCart,
|
| 191 |
-
getCartSubtotal,
|
| 192 |
-
initCart,
|
| 193 |
-
getCurrentUser
|
| 194 |
-
};
|
| 195 |
-
|
| 196 |
-
// Auto-initialize on DOMContentLoaded
|
| 197 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 198 |
-
setTimeout(async () => {
|
| 199 |
-
await initCart();
|
| 200 |
-
}, 100);
|
| 201 |
-
});
|
| 202 |
-
|
| 203 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/config/edge-functions.js
DELETED
|
@@ -1,354 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* Cellex Edge Function Client (HTTP-Only Cookie Auth — Production Standard)
|
| 3 |
-
* --------------------------------------------------------------------------
|
| 4 |
-
* NO localStorage. NO sessionStorage. NO cookies in JavaScript. NO tokens.
|
| 5 |
-
*
|
| 6 |
-
* This is the same approach used by AliExpress, Temu, Alibaba, Amazon,
|
| 7 |
-
* Gmail, and Netflix — HTTP-only cookies that JavaScript CANNOT read.
|
| 8 |
-
*
|
| 9 |
-
* How it works:
|
| 10 |
-
* - Login: POST /api/auth → server creates session in Supabase → sets
|
| 11 |
-
* HTTP-only cookie with session_id → returns { user } (no session_id in JS)
|
| 12 |
-
* - All requests: browser AUTOMATICALLY sends the HTTP-only cookie →
|
| 13 |
-
* server reads it → forwards as Authorization: Bearer <session_id>
|
| 14 |
-
* - checkSession(): POST /api/auth { op: 'session' } → server reads cookie
|
| 15 |
-
* → edge function verifies → returns { user }
|
| 16 |
-
* - Logout: POST /api/auth { op: 'logout' } → server clears cookie
|
| 17 |
-
*
|
| 18 |
-
* Security:
|
| 19 |
-
* - HTTP-only cookie: JavaScript CANNOT read it (document.cookie returns nothing)
|
| 20 |
-
* - Secure: only sent over HTTPS
|
| 21 |
-
* - SameSite=Lax: CSRF protection
|
| 22 |
-
* - 7-day expiry: persists across tabs, browser restarts, and 7 days
|
| 23 |
-
* - The cookie only contains a random UUID (session_id), NOT a JWT token
|
| 24 |
-
* - The actual JWT tokens live in Supabase web_sessions table
|
| 25 |
-
* - NO localStorage. NO sessionStorage. NO tokens in JavaScript.
|
| 26 |
-
*
|
| 27 |
-
* The frontend has ZERO knowledge of the session_id. It just calls API
|
| 28 |
-
* endpoints and the browser handles the cookie automatically.
|
| 29 |
-
*/
|
| 30 |
-
|
| 31 |
-
(function() {
|
| 32 |
-
'use strict';
|
| 33 |
-
|
| 34 |
-
// User cached in memory (NOT in localStorage/sessionStorage)
|
| 35 |
-
// This is just a cache so we don't call checkSession() on every render.
|
| 36 |
-
// The actual auth state is determined by the HTTP-only cookie.
|
| 37 |
-
let currentUser = null;
|
| 38 |
-
let sessionVerified = false;
|
| 39 |
-
|
| 40 |
-
/**
|
| 41 |
-
* Call an API endpoint. The browser automatically sends the HTTP-only
|
| 42 |
-
* cookie with every request — we don't need to attach anything.
|
| 43 |
-
*/
|
| 44 |
-
async function call(path, body = {}) {
|
| 45 |
-
const headers = {
|
| 46 |
-
'Content-Type': 'application/json',
|
| 47 |
-
};
|
| 48 |
-
|
| 49 |
-
try {
|
| 50 |
-
const resp = await fetch(`/api/${path}`, {
|
| 51 |
-
method: 'POST',
|
| 52 |
-
headers,
|
| 53 |
-
body: JSON.stringify(body),
|
| 54 |
-
credentials: 'same-origin', // Send cookies for same-origin
|
| 55 |
-
});
|
| 56 |
-
|
| 57 |
-
const data = await resp.json();
|
| 58 |
-
|
| 59 |
-
if (!resp.ok) {
|
| 60 |
-
console.error(`[EdgeFunctions] /api/${path} failed:`, resp.status, data);
|
| 61 |
-
return { success: false, error: data.error || 'Request failed', status: resp.status };
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
return data;
|
| 65 |
-
} catch (error) {
|
| 66 |
-
console.error(`[EdgeFunctions] /api/${path} network error:`, error);
|
| 67 |
-
return { success: false, error: error.message || 'Network error' };
|
| 68 |
-
}
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
// ---- Auth operations ----
|
| 72 |
-
|
| 73 |
-
async function login(email, password) {
|
| 74 |
-
const result = await call('auth', { op: 'login', email, password });
|
| 75 |
-
|
| 76 |
-
// Server sets HTTP-only cookie. We just cache the user.
|
| 77 |
-
if (result.success && result.user) {
|
| 78 |
-
currentUser = result.user;
|
| 79 |
-
sessionVerified = true;
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
return result;
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
async function signup(email, password) {
|
| 86 |
-
const result = await call('auth', { op: 'signup', email, password });
|
| 87 |
-
|
| 88 |
-
if (result.success && result.user) {
|
| 89 |
-
currentUser = result.user;
|
| 90 |
-
sessionVerified = true;
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
return result;
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
async function logout() {
|
| 97 |
-
const result = await call('auth', { op: 'logout' });
|
| 98 |
-
// Server clears the HTTP-only cookie.
|
| 99 |
-
currentUser = null;
|
| 100 |
-
sessionVerified = false;
|
| 101 |
-
return result;
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
/**
|
| 105 |
-
* Check if user is logged in by asking the server.
|
| 106 |
-
* The server reads the HTTP-only cookie (which JS can't see) and
|
| 107 |
-
* verifies the session with the edge function.
|
| 108 |
-
*
|
| 109 |
-
* Call this on EVERY page load to restore the user session.
|
| 110 |
-
* The browser auto-sends the cookie, so this works across tabs and
|
| 111 |
-
* browser restarts.
|
| 112 |
-
*/
|
| 113 |
-
async function checkSession() {
|
| 114 |
-
// If we already verified this session and have a user, return cached
|
| 115 |
-
if (sessionVerified && currentUser) {
|
| 116 |
-
return { success: true, user: currentUser };
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
// Ask the server to check the HTTP-only cookie
|
| 120 |
-
const result = await call('auth', { op: 'session' });
|
| 121 |
-
sessionVerified = true;
|
| 122 |
-
|
| 123 |
-
if (result.success && result.user) {
|
| 124 |
-
currentUser = result.user;
|
| 125 |
-
return { success: true, user: currentUser };
|
| 126 |
-
}
|
| 127 |
-
|
| 128 |
-
currentUser = null;
|
| 129 |
-
return { success: true, user: null };
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
function getCurrentUser() {
|
| 133 |
-
return currentUser;
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
async function getCurrentUserAsync() {
|
| 137 |
-
if (currentUser) return currentUser;
|
| 138 |
-
await checkSession();
|
| 139 |
-
return currentUser;
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
function isLoggedIn() {
|
| 143 |
-
return !!currentUser;
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
// Expose the EdgeFunctions module globally
|
| 147 |
-
window.EdgeFunctions = {
|
| 148 |
-
auth: {
|
| 149 |
-
login,
|
| 150 |
-
signup,
|
| 151 |
-
logout,
|
| 152 |
-
checkSession,
|
| 153 |
-
getCurrentUser,
|
| 154 |
-
getCurrentUserAsync,
|
| 155 |
-
isLoggedIn,
|
| 156 |
-
},
|
| 157 |
-
|
| 158 |
-
aiChat: (message, context = {}) => call('ai-chat', { message, context }),
|
| 159 |
-
|
| 160 |
-
cart: {
|
| 161 |
-
get: () => call('cart', { op: 'get' }),
|
| 162 |
-
count: () => call('cart', { op: 'count' }),
|
| 163 |
-
add: (productId, quantity = 1) => call('cart', { op: 'add', productId, quantity }),
|
| 164 |
-
remove: (cartItemId) => call('cart', { op: 'remove', cartItemId }),
|
| 165 |
-
update: (cartItemId, quantity) => call('cart', { op: 'update', cartItemId, quantity }),
|
| 166 |
-
clear: () => call('cart', { op: 'clear' }),
|
| 167 |
-
},
|
| 168 |
-
|
| 169 |
-
products: {
|
| 170 |
-
home: () => call('products', { op: 'home' }),
|
| 171 |
-
search: (query, maxPrice = null) => call('products', { op: 'search', query, maxPrice }),
|
| 172 |
-
category: (category, sort = 'newest', page = 1) => call('products', { op: 'category', category, sort, page }),
|
| 173 |
-
byId: (id) => call('products', { op: 'by_id', id }),
|
| 174 |
-
all: (limit = 100) => call('products', { op: 'all', limit }),
|
| 175 |
-
},
|
| 176 |
-
|
| 177 |
-
orders: {
|
| 178 |
-
list: () => call('orders', { op: 'list' }),
|
| 179 |
-
details: (orderId) => call('orders', { op: 'details', orderId }),
|
| 180 |
-
},
|
| 181 |
-
|
| 182 |
-
profile: {
|
| 183 |
-
get: () => call('profile', { op: 'get' }),
|
| 184 |
-
update: (data) => call('profile', { op: 'update', ...data }),
|
| 185 |
-
},
|
| 186 |
-
|
| 187 |
-
wishlist: {
|
| 188 |
-
get: () => call('wishlist', { op: 'get' }),
|
| 189 |
-
add: (productId) => call('wishlist', { op: 'add', productId }),
|
| 190 |
-
remove: (wishlistItemId) => call('wishlist', { op: 'remove', wishlistItemId }),
|
| 191 |
-
},
|
| 192 |
-
|
| 193 |
-
checkout: {
|
| 194 |
-
prepare: () => call('checkout', { op: 'prepare' }),
|
| 195 |
-
placeOrder: (shippingAddress) => call('checkout', { op: 'place_order', shippingAddress }),
|
| 196 |
-
},
|
| 197 |
-
|
| 198 |
-
// ---- Seller dashboard (Phase 0 — migrated from "Eesha selling folder") ----
|
| 199 |
-
sellerDashboard: {
|
| 200 |
-
stats: () => call('seller-dashboard', { op: 'stats' }),
|
| 201 |
-
recent: () => call('seller-dashboard', { op: 'recent' }),
|
| 202 |
-
notifications: () => call('seller-dashboard', { op: 'notifications' }),
|
| 203 |
-
},
|
| 204 |
-
|
| 205 |
-
sellerProducts: {
|
| 206 |
-
list: () => call('seller-products', { op: 'list' }),
|
| 207 |
-
create: (data) => call('seller-products', { op: 'create', ...data }),
|
| 208 |
-
update: (id, data) => call('seller-products', { op: 'update', id, ...data }),
|
| 209 |
-
delete: (id) => call('seller-products', { op: 'delete', id }),
|
| 210 |
-
},
|
| 211 |
-
|
| 212 |
-
sellerOrders: {
|
| 213 |
-
list: () => call('seller-orders', { op: 'list' }),
|
| 214 |
-
details: (orderId) => call('seller-orders', { op: 'details', orderId }),
|
| 215 |
-
},
|
| 216 |
-
|
| 217 |
-
sellerProfile: {
|
| 218 |
-
get: () => call('seller-profile', { op: 'get' }),
|
| 219 |
-
update: (data) => call('seller-profile', { op: 'update', ...data }),
|
| 220 |
-
},
|
| 221 |
-
|
| 222 |
-
// ---- Social (Phase 1) ----
|
| 223 |
-
social: {
|
| 224 |
-
// Public — no auth required
|
| 225 |
-
publicProfile: (sellerId, viewerId = null) => call('social', { op: 'public_profile', sellerId, viewerId }),
|
| 226 |
-
sellerFeed: (sellerId, limit = 20) => call('social', { op: 'seller_feed', sellerId, limit }),
|
| 227 |
-
discover: (limit = 12) => call('social', { op: 'discover', limit }),
|
| 228 |
-
|
| 229 |
-
// Auth required
|
| 230 |
-
follow: (sellerId) => call('social', { op: 'follow', sellerId }),
|
| 231 |
-
unfollow: (sellerId) => call('social', { op: 'unfollow', sellerId }),
|
| 232 |
-
following: () => call('social', { op: 'following' }),
|
| 233 |
-
feed: (limit = 20, offset = 0) => call('social', { op: 'feed', limit, offset }),
|
| 234 |
-
},
|
| 235 |
-
|
| 236 |
-
// ---- Phase 2: Reviews & Ratings ----
|
| 237 |
-
reviews: {
|
| 238 |
-
byProduct: (productId) => call('reviews', { op: 'by_product', productId }),
|
| 239 |
-
bySeller: (sellerId) => call('reviews', { op: 'by_seller', sellerId }),
|
| 240 |
-
create: (data) => call('reviews', { op: 'create', ...data }),
|
| 241 |
-
helpful: (reviewId) => call('reviews', { op: 'helpful', reviewId }),
|
| 242 |
-
delete: (reviewId) => call('reviews', { op: 'delete', reviewId }),
|
| 243 |
-
},
|
| 244 |
-
|
| 245 |
-
// ---- Phase 2: Group Buying (Pinduoduo) ----
|
| 246 |
-
groupBuy: {
|
| 247 |
-
create: (productId, targetCount = 3, discountPct = 20) =>
|
| 248 |
-
call('group-buy', { op: 'create', productId, targetCount, discountPct }),
|
| 249 |
-
join: (groupBuyId) => call('group-buy', { op: 'join', groupBuyId }),
|
| 250 |
-
status: (groupBuyId) => call('group-buy', { op: 'status', groupBuyId }),
|
| 251 |
-
active: (productId) => call('group-buy', { op: 'active', productId }),
|
| 252 |
-
mine: () => call('group-buy', { op: 'mine' }),
|
| 253 |
-
cancel: (groupBuyId) => call('group-buy', { op: 'cancel', groupBuyId }),
|
| 254 |
-
},
|
| 255 |
-
|
| 256 |
-
// ---- Phase 2: Wishlist Sharing ----
|
| 257 |
-
wishlistShare: {
|
| 258 |
-
share: (title = null) => call('wishlist-share', { op: 'share', title }),
|
| 259 |
-
getShared: (token) => call('wishlist-share', { op: 'get_shared', token }),
|
| 260 |
-
myShares: () => call('wishlist-share', { op: 'my_shares' }),
|
| 261 |
-
revoke: (token) => call('wishlist-share', { op: 'revoke', token }),
|
| 262 |
-
},
|
| 263 |
-
|
| 264 |
-
// ---- Phase 2: Live Shopping (Whatnot) ----
|
| 265 |
-
live: {
|
| 266 |
-
// Public
|
| 267 |
-
list: (status = 'live') => call('live', { op: 'list', status }),
|
| 268 |
-
get: (sessionId) => call('live', { op: 'get', sessionId }),
|
| 269 |
-
messages: (sessionId, afterId = 0) => call('live', { op: 'messages', sessionId, afterId }),
|
| 270 |
-
|
| 271 |
-
// Auth
|
| 272 |
-
start: (data) => call('live', { op: 'start', ...data }),
|
| 273 |
-
end: (sessionId) => call('live', { op: 'end', sessionId }),
|
| 274 |
-
join: (sessionId, name = null) => call('live', { op: 'join', sessionId, name }),
|
| 275 |
-
leave: (sessionId) => call('live', { op: 'leave', sessionId }),
|
| 276 |
-
message: (sessionId, message) => call('live', { op: 'message', sessionId, message }),
|
| 277 |
-
myNext: () => call('live', { op: 'mynext' }),
|
| 278 |
-
},
|
| 279 |
-
|
| 280 |
-
// ---- Phase 3: Short Product Videos (TikTok Shop) ----
|
| 281 |
-
videos: {
|
| 282 |
-
// Public
|
| 283 |
-
feed: (limit = 20) => call('videos', { op: 'feed', limit }),
|
| 284 |
-
byProduct: (productId) => call('videos', { op: 'by_product', productId }),
|
| 285 |
-
bySeller: (sellerId) => call('videos', { op: 'by_seller', sellerId }),
|
| 286 |
-
get: (videoId) => call('videos', { op: 'get', videoId }),
|
| 287 |
-
|
| 288 |
-
// Auth (seller)
|
| 289 |
-
uploadUrl: (productId, filename, mime = 'video/mp4') => call('videos', { op: 'upload_url', productId, filename, mime }),
|
| 290 |
-
create: (data) => call('videos', { op: 'create', ...data }),
|
| 291 |
-
delete: (videoId) => call('videos', { op: 'delete', videoId }),
|
| 292 |
-
mine: () => call('videos', { op: 'mine' }),
|
| 293 |
-
|
| 294 |
-
// Auth (buyer)
|
| 295 |
-
like: (videoId) => call('videos', { op: 'like', videoId }),
|
| 296 |
-
unlike: (videoId) => call('videos', { op: 'unlike', videoId }),
|
| 297 |
-
},
|
| 298 |
-
|
| 299 |
-
// ---- Phase 3: Trending Feed ----
|
| 300 |
-
trending: {
|
| 301 |
-
list: (limit = 20, hours = 24) => call('trending', { op: 'list', limit, hours }),
|
| 302 |
-
logView: (productId, source = 'product_page') => call('trending', { op: 'log_view', productId, source }),
|
| 303 |
-
logShare: (productId, platform = 'whatsapp') => call('trending', { op: 'log_share', productId, platform }),
|
| 304 |
-
},
|
| 305 |
-
|
| 306 |
-
// ---- Phase 3: Seller Stories (Instagram-style) ----
|
| 307 |
-
stories: {
|
| 308 |
-
// Public
|
| 309 |
-
activeBar: () => call('stories', { op: 'active_bar' }),
|
| 310 |
-
bySeller: (sellerId) => call('stories', { op: 'by_seller', sellerId }),
|
| 311 |
-
get: (storyId) => call('stories', { op: 'get', storyId }),
|
| 312 |
-
|
| 313 |
-
// Auth
|
| 314 |
-
markSeen: (storyIds) => call('stories', { op: 'mark_seen', storyIds }),
|
| 315 |
-
create: (data) => call('stories', { op: 'create', ...data }),
|
| 316 |
-
mine: () => call('stories', { op: 'mine' }),
|
| 317 |
-
delete: (storyId) => call('stories', { op: 'delete', storyId }),
|
| 318 |
-
},
|
| 319 |
-
|
| 320 |
-
// ---- Phase 3: AI-Powered Discovery ----
|
| 321 |
-
discover: {
|
| 322 |
-
recommend: (limit = 10) => call('discover', { op: 'recommend', limit }),
|
| 323 |
-
logView: (productId) => call('discover', { op: 'log_view', productId }),
|
| 324 |
-
},
|
| 325 |
-
|
| 326 |
-
// ---- Phase 4: Cross-platform integration ----
|
| 327 |
-
crossPlatform: {
|
| 328 |
-
// Web-side (cookie auth)
|
| 329 |
-
generateLinkCode: (phone) => call('cross-platform', { op: 'generate_link_code', phone }),
|
| 330 |
-
myPhoneLinks: () => call('cross-platform', { op: 'my_phone_links' }),
|
| 331 |
-
unlinkPhone: (phone) => call('cross-platform', { op: 'unlink_phone', phone }),
|
| 332 |
-
},
|
| 333 |
-
|
| 334 |
-
// ---- Payment Gateway (PalmPay verification) ----
|
| 335 |
-
payment: {
|
| 336 |
-
createOrder: (data) => call('payment', { op: 'create_order', ...data }),
|
| 337 |
-
confirmSent: (orderId) => call('payment', { op: 'confirm_sent', orderId }),
|
| 338 |
-
checkStatus: (orderId) => call('payment', { op: 'check_status', orderId }),
|
| 339 |
-
},
|
| 340 |
-
|
| 341 |
-
telegram: {
|
| 342 |
-
channelInfo: () => call('telegram', { op: 'channel_info' }),
|
| 343 |
-
recent: () => call('telegram', { op: 'recent' }),
|
| 344 |
-
subscribe: (chatId, username = null, userId = null) =>
|
| 345 |
-
call('telegram', { op: 'subscribe', chatId, username, userId }),
|
| 346 |
-
unsubscribe: (chatId) => call('telegram', { op: 'unsubscribe', chatId }),
|
| 347 |
-
setWebhook: (url) => call('telegram', { op: 'set_webhook', url }),
|
| 348 |
-
},
|
| 349 |
-
|
| 350 |
-
call,
|
| 351 |
-
};
|
| 352 |
-
|
| 353 |
-
console.log('[EdgeFunctions] HTTP-only cookie auth initialized (no localStorage, no sessionStorage, no tokens in JS)');
|
| 354 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/load-navbar.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
| 1 |
-
// Cellex Navbar Loader — properly executes scripts inside injected HTML
|
| 2 |
-
// Usage: <div id="nav-mount"></div>
|
| 3 |
-
// <script src="js/load-navbar.js"></script>
|
| 4 |
-
(function() {
|
| 5 |
-
fetch('shared/navbar.html')
|
| 6 |
-
.then(r => r.text())
|
| 7 |
-
.then(html => {
|
| 8 |
-
const mount = document.getElementById('nav-mount');
|
| 9 |
-
if (!mount) return;
|
| 10 |
-
mount.innerHTML = html;
|
| 11 |
-
// Extract and execute all <script> tags inside the injected HTML
|
| 12 |
-
// (innerHTML doesn't execute scripts — this is a browser security feature)
|
| 13 |
-
mount.querySelectorAll('script').forEach(oldScript => {
|
| 14 |
-
const newScript = document.createElement('script');
|
| 15 |
-
if (oldScript.src) {
|
| 16 |
-
newScript.src = oldScript.src;
|
| 17 |
-
} else {
|
| 18 |
-
newScript.textContent = oldScript.textContent;
|
| 19 |
-
}
|
| 20 |
-
oldScript.parentNode.replaceChild(newScript, oldScript);
|
| 21 |
-
});
|
| 22 |
-
});
|
| 23 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/loader.js
DELETED
|
File without changes
|
js/main.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
| 1 |
-
// Sample product data (will be replaced with actual data from backend)
|
| 2 |
-
const sampleProducts = [
|
| 3 |
-
{
|
| 4 |
-
id: 1,
|
| 5 |
-
name: "Wireless Headphones",
|
| 6 |
-
price: 59.99,
|
| 7 |
-
image: "path/to/headphones.jpg",
|
| 8 |
-
category: "Electronics"
|
| 9 |
-
},
|
| 10 |
-
// More products will be added
|
| 11 |
-
];
|
| 12 |
-
|
| 13 |
-
// Hero slider functionality
|
| 14 |
-
function initializeHeroSlider() {
|
| 15 |
-
const heroSlider = document.querySelector('.hero-slider');
|
| 16 |
-
// Add slider implementation here
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
// Product display functionality
|
| 20 |
-
function displayProducts(products) {
|
| 21 |
-
const productGrid = document.getElementById('featuredProducts');
|
| 22 |
-
productGrid.innerHTML = '';
|
| 23 |
-
|
| 24 |
-
products.forEach(product => {
|
| 25 |
-
const productCard = document.createElement('div');
|
| 26 |
-
productCard.className = 'product-card';
|
| 27 |
-
productCard.innerHTML = `
|
| 28 |
-
<img src="${product.image}" alt="${product.name}">
|
| 29 |
-
<h3>${product.name}</h3>
|
| 30 |
-
<p class="price">$${product.price}</p>
|
| 31 |
-
<button onclick="viewProduct(${product.id})" class="view-btn">View Details</button>
|
| 32 |
-
`;
|
| 33 |
-
productGrid.appendChild(productCard);
|
| 34 |
-
});
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
// Product view functionality
|
| 38 |
-
function viewProduct(productId) {
|
| 39 |
-
// Check if user is logged in
|
| 40 |
-
const isLoggedIn = checkLoginStatus();
|
| 41 |
-
if (!isLoggedIn) {
|
| 42 |
-
window.location.href = 'login.html';
|
| 43 |
-
return;
|
| 44 |
-
}
|
| 45 |
-
// If logged in, show product details
|
| 46 |
-
// Implementation will be added
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
// Login status check
|
| 50 |
-
function checkLoginStatus() {
|
| 51 |
-
// This will be implemented with actual session management
|
| 52 |
-
return false;
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
// AI Shopping Assistant - DOM elements are queried after DOMContentLoaded
|
| 56 |
-
|
| 57 |
-
// Add message to chat
|
| 58 |
-
function addMessageToChat(sender, message) {
|
| 59 |
-
const messageDiv = document.createElement('div');
|
| 60 |
-
messageDiv.className = `message ${sender}-message`;
|
| 61 |
-
messageDiv.textContent = message;
|
| 62 |
-
chatMessages.appendChild(messageDiv);
|
| 63 |
-
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
// Simulate AI response (will be replaced with actual AI implementation)
|
| 67 |
-
function simulateAIResponse(userMessage) {
|
| 68 |
-
setTimeout(() => {
|
| 69 |
-
const response = "I can help you find products based on your preferences. Could you tell me more about what you're looking for?";
|
| 70 |
-
addMessageToChat('ai', response);
|
| 71 |
-
}, 1000);
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
// Initialize the page
|
| 75 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 76 |
-
initializeHeroSlider();
|
| 77 |
-
displayProducts(sampleProducts);
|
| 78 |
-
|
| 79 |
-
// Initialize AI chat elements safely (guard against missing elements)
|
| 80 |
-
const aiChatModal = document.getElementById('ai-chat-modal');
|
| 81 |
-
const openAIChatBtn = document.getElementById('openAIChat');
|
| 82 |
-
const closeModal = document.querySelector('.close-modal');
|
| 83 |
-
const chatMessages = document.getElementById('chatMessages');
|
| 84 |
-
const userInput = document.getElementById('userInput');
|
| 85 |
-
const sendMessageBtn = document.getElementById('sendMessage');
|
| 86 |
-
|
| 87 |
-
if (openAIChatBtn && aiChatModal) {
|
| 88 |
-
openAIChatBtn.addEventListener('click', () => {
|
| 89 |
-
aiChatModal.style.display = 'block';
|
| 90 |
-
});
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
if (closeModal && aiChatModal) {
|
| 94 |
-
closeModal.addEventListener('click', () => {
|
| 95 |
-
aiChatModal.style.display = 'none';
|
| 96 |
-
});
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
// Close modal when clicking outside (guarded)
|
| 100 |
-
if (aiChatModal) {
|
| 101 |
-
window.addEventListener('click', (event) => {
|
| 102 |
-
if (event.target === aiChatModal) aiChatModal.style.display = 'none';
|
| 103 |
-
});
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
// Send message in AI chat (guarded)
|
| 107 |
-
if (sendMessageBtn && userInput && chatMessages) {
|
| 108 |
-
sendMessageBtn.addEventListener('click', () => {
|
| 109 |
-
const message = userInput.value.trim();
|
| 110 |
-
if (message) {
|
| 111 |
-
addMessageToChat('user', message);
|
| 112 |
-
simulateAIResponse(message);
|
| 113 |
-
userInput.value = '';
|
| 114 |
-
}
|
| 115 |
-
});
|
| 116 |
-
}
|
| 117 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/product.js
DELETED
|
@@ -1,536 +0,0 @@
|
|
| 1 |
-
// Product detail page logic — uses EdgeFunctions (cookie-based auth, no Supabase SDK).
|
| 2 |
-
// Requires: ../js/config/edge-functions.js and ../js/cart.js loaded before this file.
|
| 3 |
-
|
| 4 |
-
// Get product ID from URL
|
| 5 |
-
function getProductIdFromUrl() {
|
| 6 |
-
const urlParams = new URLSearchParams(window.location.search);
|
| 7 |
-
return urlParams.get('id');
|
| 8 |
-
}
|
| 9 |
-
|
| 10 |
-
// Load product details via edge function
|
| 11 |
-
async function loadProductDetails() {
|
| 12 |
-
const productId = getProductIdFromUrl();
|
| 13 |
-
if (!productId) {
|
| 14 |
-
window.location.href = '/index.html';
|
| 15 |
-
return;
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
try {
|
| 19 |
-
// Show loading states
|
| 20 |
-
document.getElementById('loadingSkeleton').classList.remove('hidden');
|
| 21 |
-
document.getElementById('loadingText').classList.remove('hidden');
|
| 22 |
-
document.getElementById('productImagesContainer').classList.add('hidden');
|
| 23 |
-
document.getElementById('productInfo').classList.add('hidden');
|
| 24 |
-
document.getElementById('priceSection').classList.add('hidden');
|
| 25 |
-
document.getElementById('descriptionSection').classList.add('hidden');
|
| 26 |
-
|
| 27 |
-
// Fetch product via edge function
|
| 28 |
-
const result = await window.EdgeFunctions.products.byId(productId);
|
| 29 |
-
|
| 30 |
-
if (!result.success || !result.product) {
|
| 31 |
-
console.error('Error loading product:', result.error);
|
| 32 |
-
window.location.href = '/index.html';
|
| 33 |
-
return;
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
const product = result.product;
|
| 37 |
-
window._currentProduct = product; // cache for share functions
|
| 38 |
-
|
| 39 |
-
// Hide loading states
|
| 40 |
-
document.getElementById('loadingSkeleton').classList.add('hidden');
|
| 41 |
-
document.getElementById('loadingText').classList.add('hidden');
|
| 42 |
-
document.getElementById('productImagesContainer').classList.remove('hidden');
|
| 43 |
-
document.getElementById('productInfo').classList.remove('hidden');
|
| 44 |
-
document.getElementById('priceSection').classList.remove('hidden');
|
| 45 |
-
document.getElementById('descriptionSection').classList.remove('hidden');
|
| 46 |
-
|
| 47 |
-
// Update the UI with product details
|
| 48 |
-
document.getElementById('mainImage').src = product.image_url;
|
| 49 |
-
document.getElementById('productName').textContent = product.name;
|
| 50 |
-
document.getElementById('productCategory').textContent = product.category || 'General';
|
| 51 |
-
document.getElementById('productPrice').textContent = `₦${product.price.toFixed(2)}`;
|
| 52 |
-
document.getElementById('productDescription').textContent = product.description;
|
| 53 |
-
|
| 54 |
-
// Handle optional fields
|
| 55 |
-
if (product.original_price) {
|
| 56 |
-
document.getElementById('originalPrice').textContent = `₦${product.original_price.toFixed(2)}`;
|
| 57 |
-
const discount = Math.round(((product.original_price - product.price) / product.original_price) * 100);
|
| 58 |
-
document.getElementById('discountBadge').textContent = `-${discount}%`;
|
| 59 |
-
} else {
|
| 60 |
-
document.getElementById('originalPrice').classList.add('hidden');
|
| 61 |
-
document.getElementById('discountBadge').classList.add('hidden');
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
// Update page title
|
| 65 |
-
document.title = `${product.name} - Eesha`;
|
| 66 |
-
|
| 67 |
-
// Handle product images
|
| 68 |
-
const thumbnailContainer = document.getElementById('thumbnailContainer');
|
| 69 |
-
thumbnailContainer.innerHTML = ''; // Clear existing thumbnails
|
| 70 |
-
|
| 71 |
-
// Add main image to thumbnails
|
| 72 |
-
const mainThumbnail = createThumbnail(product.image_url, product.name, true);
|
| 73 |
-
thumbnailContainer.appendChild(mainThumbnail);
|
| 74 |
-
|
| 75 |
-
// Add additional images if available
|
| 76 |
-
if (product.additional_images && Array.isArray(product.additional_images)) {
|
| 77 |
-
product.additional_images.forEach(imgUrl => {
|
| 78 |
-
const thumbnail = createThumbnail(imgUrl, product.name);
|
| 79 |
-
thumbnailContainer.appendChild(thumbnail);
|
| 80 |
-
});
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
// Handle rating if available
|
| 84 |
-
if (product.rating) {
|
| 85 |
-
const ratingDiv = document.getElementById('productRating');
|
| 86 |
-
const stars = '★'.repeat(Math.floor(product.rating)) + '☆'.repeat(5 - Math.floor(product.rating));
|
| 87 |
-
ratingDiv.innerHTML = `
|
| 88 |
-
<div class="text-yellow-400">${stars}</div>
|
| 89 |
-
<span class="text-gray-600">(${product.rating_count || 0} reviews)</span>
|
| 90 |
-
`;
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
} catch (error) {
|
| 94 |
-
console.error('Error loading product:', error);
|
| 95 |
-
alert('Error loading product details. Please try again.');
|
| 96 |
-
}
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
// Quantity management
|
| 100 |
-
let quantity = 1;
|
| 101 |
-
|
| 102 |
-
function increaseQty() {
|
| 103 |
-
quantity++;
|
| 104 |
-
updateQuantityDisplay();
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
function decreaseQty() {
|
| 108 |
-
if (quantity > 1) {
|
| 109 |
-
quantity--;
|
| 110 |
-
updateQuantityDisplay();
|
| 111 |
-
}
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
function updateQuantityDisplay() {
|
| 115 |
-
document.getElementById('quantity').textContent = quantity;
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
// Add to cart functionality — uses Cart module (requires login, cookie-based)
|
| 119 |
-
async function addToCart() {
|
| 120 |
-
const productId = getProductIdFromUrl();
|
| 121 |
-
if (!productId) return;
|
| 122 |
-
|
| 123 |
-
try {
|
| 124 |
-
// Cart module handles auth check + cart upsert internally
|
| 125 |
-
const result = await window.Cart.addToCart(productId, quantity);
|
| 126 |
-
|
| 127 |
-
if (!result.success) {
|
| 128 |
-
if (result.requiresAuth) {
|
| 129 |
-
// Redirect to login
|
| 130 |
-
if (confirm('Please login to add items to your cart. Go to login page?')) {
|
| 131 |
-
window.location.href = `login.html?redirect=product&id=${productId}`;
|
| 132 |
-
}
|
| 133 |
-
return;
|
| 134 |
-
}
|
| 135 |
-
alert(result.message || 'Failed to add to cart. Please try again.');
|
| 136 |
-
return;
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
// Visual feedback
|
| 140 |
-
const addBtn = document.querySelector('button[onclick="addToCart()"]');
|
| 141 |
-
if (addBtn) {
|
| 142 |
-
const orig = addBtn.innerHTML;
|
| 143 |
-
addBtn.innerHTML = '<i class="fas fa-check"></i> Added!';
|
| 144 |
-
addBtn.classList.add('bg-green-500');
|
| 145 |
-
setTimeout(() => {
|
| 146 |
-
addBtn.innerHTML = orig;
|
| 147 |
-
addBtn.classList.remove('bg-green-500');
|
| 148 |
-
}, 1500);
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
} catch (error) {
|
| 152 |
-
console.error('Add to cart failed:', error);
|
| 153 |
-
alert('Failed to add to cart. Please try again.');
|
| 154 |
-
}
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
// Update cart count in header — delegates to shared Cart module
|
| 158 |
-
async function updateCartCount() {
|
| 159 |
-
try {
|
| 160 |
-
if (window.Cart && window.Cart.updateCartCountUI) {
|
| 161 |
-
await window.Cart.updateCartCountUI();
|
| 162 |
-
}
|
| 163 |
-
} catch (err) {
|
| 164 |
-
console.error('Error updating cart count:', err);
|
| 165 |
-
}
|
| 166 |
-
}
|
| 167 |
-
|
| 168 |
-
// Image gallery functionality
|
| 169 |
-
function createThumbnail(imageUrl, altText, isActive = false) {
|
| 170 |
-
const div = document.createElement('div');
|
| 171 |
-
div.className = `cursor-pointer rounded-lg overflow-hidden border-2 ${isActive ? 'border-cyan-500' : 'border-transparent'}`;
|
| 172 |
-
|
| 173 |
-
const img = document.createElement('img');
|
| 174 |
-
img.src = imageUrl;
|
| 175 |
-
img.alt = altText;
|
| 176 |
-
img.className = 'w-full h-full object-cover aspect-square';
|
| 177 |
-
|
| 178 |
-
div.appendChild(img);
|
| 179 |
-
|
| 180 |
-
div.addEventListener('click', () => {
|
| 181 |
-
// Update main image
|
| 182 |
-
updateMainImage(imageUrl);
|
| 183 |
-
|
| 184 |
-
// Update active state of thumbnails
|
| 185 |
-
document.querySelectorAll('#thumbnailContainer > div').forEach(thumb => {
|
| 186 |
-
thumb.classList.remove('border-cyan-500');
|
| 187 |
-
thumb.classList.add('border-transparent');
|
| 188 |
-
});
|
| 189 |
-
div.classList.remove('border-transparent');
|
| 190 |
-
div.classList.add('border-cyan-500');
|
| 191 |
-
});
|
| 192 |
-
|
| 193 |
-
return div;
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
function updateMainImage(imageUrl) {
|
| 197 |
-
const mainImage = document.getElementById('mainImage');
|
| 198 |
-
mainImage.src = imageUrl;
|
| 199 |
-
}
|
| 200 |
-
|
| 201 |
-
// Global user state (kept in memory only — no localStorage)
|
| 202 |
-
let currentUser = null;
|
| 203 |
-
|
| 204 |
-
// With cookie-based auth there is no guest cart in localStorage to merge.
|
| 205 |
-
// This function is retained as a no-op for backwards compatibility with the
|
| 206 |
-
// existing init flow.
|
| 207 |
-
async function mergeGuestCartAfterSignIn() {
|
| 208 |
-
return;
|
| 209 |
-
}
|
| 210 |
-
|
| 211 |
-
function updateUIforAuthState(user) {
|
| 212 |
-
const loginLink = document.getElementById('login-link');
|
| 213 |
-
const signupLink = document.getElementById('signup-link');
|
| 214 |
-
const userMenu = document.getElementById('user-menu');
|
| 215 |
-
const userEmail = document.getElementById('user-email');
|
| 216 |
-
const mobileLogin = document.querySelector('#mobile-auth-links a[href="login.html"]') || document.querySelector('#mobile-auth-links a[href="login.html"]');
|
| 217 |
-
const mobileSignup = document.querySelector('#mobile-auth-links a[href="signup.html"]') || document.querySelector('#mobile-auth-links a[href="signup.html"]');
|
| 218 |
-
const mobileLogout = document.getElementById('mobile-logout-button');
|
| 219 |
-
|
| 220 |
-
if (user) {
|
| 221 |
-
if (loginLink) loginLink.style.display = 'none';
|
| 222 |
-
if (signupLink) signupLink.style.display = 'none';
|
| 223 |
-
if (userMenu) userMenu.style.display = 'block';
|
| 224 |
-
if (userEmail) userEmail.textContent = user.email || '';
|
| 225 |
-
if (mobileLogin) mobileLogin.style.display = 'none';
|
| 226 |
-
if (mobileSignup) mobileSignup.style.display = 'none';
|
| 227 |
-
if (mobileLogout) mobileLogout.style.display = 'block';
|
| 228 |
-
} else {
|
| 229 |
-
if (loginLink) loginLink.style.display = 'block';
|
| 230 |
-
if (signupLink) signupLink.style.display = 'block';
|
| 231 |
-
if (userMenu) userMenu.style.display = 'none';
|
| 232 |
-
if (mobileLogin) mobileLogin.style.display = 'block';
|
| 233 |
-
if (mobileSignup) mobileSignup.style.display = 'block';
|
| 234 |
-
if (mobileLogout) mobileLogout.style.display = 'none';
|
| 235 |
-
}
|
| 236 |
-
}
|
| 237 |
-
|
| 238 |
-
// Initialize page and auth handling
|
| 239 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 240 |
-
// Restore session via cookie-based auth (no onAuthStateChange available)
|
| 241 |
-
try {
|
| 242 |
-
const { user } = await window.EdgeFunctions.auth.checkSession();
|
| 243 |
-
currentUser = user || null;
|
| 244 |
-
updateUIforAuthState(currentUser);
|
| 245 |
-
await updateCartCount();
|
| 246 |
-
} catch (err) {
|
| 247 |
-
console.error('Error checking initial session:', err);
|
| 248 |
-
}
|
| 249 |
-
|
| 250 |
-
// Wire up logout buttons if present (cookie-based logout clears server cookie)
|
| 251 |
-
const logoutButton = document.getElementById('logout-button');
|
| 252 |
-
const mobileLogoutButton = document.getElementById('mobile-logout-button');
|
| 253 |
-
if (logoutButton) logoutButton.addEventListener('click', async (e) => {
|
| 254 |
-
e.preventDefault();
|
| 255 |
-
await window.EdgeFunctions.auth.logout();
|
| 256 |
-
currentUser = null;
|
| 257 |
-
updateUIforAuthState(null);
|
| 258 |
-
await updateCartCount();
|
| 259 |
-
window.location.reload();
|
| 260 |
-
});
|
| 261 |
-
if (mobileLogoutButton) mobileLogoutButton.addEventListener('click', async (e) => {
|
| 262 |
-
e.preventDefault();
|
| 263 |
-
await window.EdgeFunctions.auth.logout();
|
| 264 |
-
currentUser = null;
|
| 265 |
-
updateUIforAuthState(null);
|
| 266 |
-
await updateCartCount();
|
| 267 |
-
window.location.reload();
|
| 268 |
-
});
|
| 269 |
-
|
| 270 |
-
// User menu toggle
|
| 271 |
-
const userMenuButton = document.getElementById('user-menu-button');
|
| 272 |
-
const userMenuDropdown = document.getElementById('user-menu-dropdown');
|
| 273 |
-
if (userMenuButton && userMenuDropdown) {
|
| 274 |
-
userMenuButton.addEventListener('click', () => userMenuDropdown.classList.toggle('hidden'));
|
| 275 |
-
document.addEventListener('click', (e) => {
|
| 276 |
-
if (!document.getElementById('user-menu').contains(e.target)) {
|
| 277 |
-
userMenuDropdown.classList.add('hidden');
|
| 278 |
-
}
|
| 279 |
-
});
|
| 280 |
-
}
|
| 281 |
-
|
| 282 |
-
// Mobile menu
|
| 283 |
-
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
|
| 284 |
-
const mobileMenu = document.getElementById('mobileMenu');
|
| 285 |
-
if (mobileMenuBtn && mobileMenu) mobileMenuBtn.addEventListener('click', () => mobileMenu.classList.toggle('hidden'));
|
| 286 |
-
|
| 287 |
-
// Finally load product details and update cart count
|
| 288 |
-
await loadProductDetails();
|
| 289 |
-
await updateCartCount();
|
| 290 |
-
|
| 291 |
-
// Phase 2: load reviews + active group buys for this product
|
| 292 |
-
loadProductReviews();
|
| 293 |
-
loadActiveGroupBuys();
|
| 294 |
-
});
|
| 295 |
-
|
| 296 |
-
// Search bar behavior (navigate to search-result.html with query)
|
| 297 |
-
(function setupSearch() {
|
| 298 |
-
try {
|
| 299 |
-
const searchInput = document.getElementById('searchInput');
|
| 300 |
-
if (!searchInput) return;
|
| 301 |
-
|
| 302 |
-
const goToSearch = () => {
|
| 303 |
-
const q = (searchInput.value || '').trim();
|
| 304 |
-
if (!q) return;
|
| 305 |
-
// keep relative path consistent with index/product location
|
| 306 |
-
window.location.href = `../search-result.html?q=${encodeURIComponent(q)}`;
|
| 307 |
-
};
|
| 308 |
-
|
| 309 |
-
// Enter key
|
| 310 |
-
searchInput.addEventListener('keypress', (e) => {
|
| 311 |
-
if (e.key === 'Enter') goToSearch();
|
| 312 |
-
});
|
| 313 |
-
|
| 314 |
-
// If there's an adjacent button (search icon) try to attach click
|
| 315 |
-
const btn = searchInput.parentElement?.querySelector('button');
|
| 316 |
-
if (btn) btn.addEventListener('click', goToSearch);
|
| 317 |
-
} catch (err) {
|
| 318 |
-
console.error('Search setup failed:', err);
|
| 319 |
-
}
|
| 320 |
-
})();
|
| 321 |
-
|
| 322 |
-
// ====================================================================
|
| 323 |
-
// Phase 2: Product sharing, group buy, reviews
|
| 324 |
-
// ====================================================================
|
| 325 |
-
function getCurrentProductId() {
|
| 326 |
-
return getProductIdFromUrl();
|
| 327 |
-
}
|
| 328 |
-
|
| 329 |
-
function getCurrentProduct() {
|
| 330 |
-
// Stored when loadProductDetails runs
|
| 331 |
-
return window._currentProduct || null;
|
| 332 |
-
}
|
| 333 |
-
|
| 334 |
-
// ---- Sharing ----
|
| 335 |
-
function shareProduct(platform) {
|
| 336 |
-
const id = getCurrentProductId();
|
| 337 |
-
if (!id) return;
|
| 338 |
-
const url = `${window.location.origin}/Eesha%20buying%20folder/product.html?id=${id}`;
|
| 339 |
-
const product = getCurrentProduct();
|
| 340 |
-
const text = product
|
| 341 |
-
? `Check out "${product.name}" on Cellex — ₦${Number(product.price).toFixed(2)}`
|
| 342 |
-
: 'Check out this product on Cellex';
|
| 343 |
-
|
| 344 |
-
if (platform === 'whatsapp') {
|
| 345 |
-
window.open(`https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`, '_blank');
|
| 346 |
-
} else if (platform === 'telegram') {
|
| 347 |
-
window.open(`https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`, '_blank');
|
| 348 |
-
}
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
-
async function copyShareLink() {
|
| 352 |
-
const id = getCurrentProductId();
|
| 353 |
-
if (!id) return;
|
| 354 |
-
const url = `${window.location.origin}/Eesha%20buying%20folder/product.html?id=${id}`;
|
| 355 |
-
try {
|
| 356 |
-
await navigator.clipboard.writeText(url);
|
| 357 |
-
const btn = event?.target?.closest('button');
|
| 358 |
-
if (btn) {
|
| 359 |
-
const orig = btn.innerHTML;
|
| 360 |
-
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
| 361 |
-
setTimeout(() => btn.innerHTML = orig, 1500);
|
| 362 |
-
}
|
| 363 |
-
} catch (e) {
|
| 364 |
-
window.prompt('Copy this link:', url);
|
| 365 |
-
}
|
| 366 |
-
}
|
| 367 |
-
|
| 368 |
-
// ---- Group buy ----
|
| 369 |
-
async function startGroupBuy() {
|
| 370 |
-
const productId = getCurrentProductId();
|
| 371 |
-
if (!productId) return;
|
| 372 |
-
if (!currentUser) {
|
| 373 |
-
if (confirm('Please login to start a group buy. Go to login page?')) {
|
| 374 |
-
window.location.href = `login.html?next=${encodeURIComponent('/product.html?id=' + productId)}`;
|
| 375 |
-
}
|
| 376 |
-
return;
|
| 377 |
-
}
|
| 378 |
-
if (!confirm('Start a group buy?\n\nGet 3 friends to join and everyone gets 20% off.\n\nWe will generate a share link for you.')) return;
|
| 379 |
-
const r = await window.EdgeFunctions.groupBuy.create(Number(productId), 3, 20);
|
| 380 |
-
if (r.success) {
|
| 381 |
-
const url = `${window.location.origin}/group-buy.html?id=${r.groupBuy.id}`;
|
| 382 |
-
const shareText = `🛍️ Join my group buy on Cellex! Get 20% off when 3 of us join. ${url}`;
|
| 383 |
-
window.open(`https://wa.me/?text=${encodeURIComponent(shareText)}`, '_blank');
|
| 384 |
-
// Also navigate to the group buy page
|
| 385 |
-
window.location.href = `/group-buy.html?id=${r.groupBuy.id}`;
|
| 386 |
-
} else {
|
| 387 |
-
alert('Failed to start group buy: ' + (r.error || 'Unknown error'));
|
| 388 |
-
}
|
| 389 |
-
}
|
| 390 |
-
|
| 391 |
-
async function loadActiveGroupBuys() {
|
| 392 |
-
const productId = getCurrentProductId();
|
| 393 |
-
if (!productId) return;
|
| 394 |
-
const container = document.getElementById('activeGroupBuys');
|
| 395 |
-
if (!container) return;
|
| 396 |
-
try {
|
| 397 |
-
const r = await window.EdgeFunctions.groupBuy.active(Number(productId));
|
| 398 |
-
if (!r.success || !r.groupBuys || r.groupBuys.length === 0) return;
|
| 399 |
-
container.classList.remove('hidden');
|
| 400 |
-
container.innerHTML = `
|
| 401 |
-
<div class="bg-cyan-50 border border-cyan-200 rounded-lg p-3">
|
| 402 |
-
<div class="text-xs font-bold text-cyan-700 uppercase tracking-wide mb-2">
|
| 403 |
-
<i class="fas fa-fire"></i> ${r.groupBuys.length} active group buy${r.groupBuys.length > 1 ? 's' : ''} — join & get 20% off!
|
| 404 |
-
</div>
|
| 405 |
-
${r.groupBuys.map(gb => `
|
| 406 |
-
<a href="/group-buy.html?id=${gb.id}" class="block text-sm text-amber-900 hover:underline">
|
| 407 |
-
👥 ${gb.current_count}/${gb.target_count} joined · expires ${new Date(gb.expires_at).toLocaleString()}
|
| 408 |
-
</a>
|
| 409 |
-
`).join('')}
|
| 410 |
-
</div>`;
|
| 411 |
-
} catch (e) { /* silent fail */ }
|
| 412 |
-
}
|
| 413 |
-
|
| 414 |
-
// ---- Reviews ----
|
| 415 |
-
async function loadProductReviews() {
|
| 416 |
-
const productId = getCurrentProductId();
|
| 417 |
-
if (!productId) return;
|
| 418 |
-
try {
|
| 419 |
-
const r = await window.EdgeFunctions.reviews.byProduct(Number(productId));
|
| 420 |
-
if (!r.success) return;
|
| 421 |
-
|
| 422 |
-
// Summary
|
| 423 |
-
const summary = document.getElementById('reviewsSummary');
|
| 424 |
-
if (r.summary.count > 0) {
|
| 425 |
-
summary.classList.remove('hidden');
|
| 426 |
-
document.getElementById('reviewsAvg').textContent = Number(r.summary.avg).toFixed(1);
|
| 427 |
-
document.getElementById('reviewsStars').textContent =
|
| 428 |
-
'★'.repeat(Math.round(r.summary.avg)) + '☆'.repeat(5 - Math.round(r.summary.avg));
|
| 429 |
-
document.getElementById('reviewsCount').textContent = `${r.summary.count} review${r.summary.count > 1 ? 's' : ''}`;
|
| 430 |
-
document.getElementById('reviewsBlurb').textContent =
|
| 431 |
-
r.summary.avg >= 4 ? 'Buyers love this product!' :
|
| 432 |
-
r.summary.avg >= 3 ? 'Most buyers are satisfied.' :
|
| 433 |
-
'Mixed reviews — read below.';
|
| 434 |
-
}
|
| 435 |
-
|
| 436 |
-
// List
|
| 437 |
-
const list = document.getElementById('reviewsList');
|
| 438 |
-
if (!r.reviews || r.reviews.length === 0) {
|
| 439 |
-
list.innerHTML = '<div class="text-gray-500 text-sm">No reviews yet. Be the first to write one!</div>';
|
| 440 |
-
return;
|
| 441 |
-
}
|
| 442 |
-
list.innerHTML = r.reviews.map(rev => `
|
| 443 |
-
<div class="border-b pb-3">
|
| 444 |
-
<div class="flex items-center justify-between">
|
| 445 |
-
<div class="flex items-center gap-2">
|
| 446 |
-
<div class="w-8 h-8 rounded-full bg-cyan-100 text-cyan-700 flex items-center justify-center font-bold text-sm">
|
| 447 |
-
${(rev.reviewer_name || 'B').charAt(0).toUpperCase()}
|
| 448 |
-
</div>
|
| 449 |
-
<div>
|
| 450 |
-
<div class="font-medium text-sm">${escapeHtml(rev.reviewer_name || 'Buyer')}</div>
|
| 451 |
-
<div class="text-xs text-gray-500">${new Date(rev.created_at).toLocaleDateString()}</div>
|
| 452 |
-
</div>
|
| 453 |
-
</div>
|
| 454 |
-
<div class="text-yellow-400 text-sm">${'★'.repeat(rev.rating)}${'☆'.repeat(5 - rev.rating)}</div>
|
| 455 |
-
</div>
|
| 456 |
-
${rev.title ? `<div class="font-semibold mt-2">${escapeHtml(rev.title)}</div>` : ''}
|
| 457 |
-
${rev.comment ? `<p class="text-gray-700 text-sm mt-1">${escapeHtml(rev.comment)}</p>` : ''}
|
| 458 |
-
${rev.verified_purchase ? '<div class="text-xs text-green-600 mt-1"><i class="fas fa-check-circle"></i> Verified purchase</div>' : ''}
|
| 459 |
-
${rev.images && rev.images.length ? `<div class="flex gap-2 mt-2">${rev.images.map(img => `<img src="${escapeHtml(img)}" class="w-16 h-16 object-cover rounded">`).join('')}</div>` : ''}
|
| 460 |
-
<button onclick="markHelpful('${rev.id}')" class="text-xs text-gray-500 hover:text-cyan-600 mt-2">
|
| 461 |
-
<i class="fas fa-thumbs-up"></i> Helpful (${rev.helpful_count || 0})
|
| 462 |
-
</button>
|
| 463 |
-
</div>`).join('');
|
| 464 |
-
} catch (e) {
|
| 465 |
-
console.error('reviews load error', e);
|
| 466 |
-
}
|
| 467 |
-
}
|
| 468 |
-
|
| 469 |
-
function escapeHtml(s) {
|
| 470 |
-
return String(s ?? '').replace(/[&<>"']/g, c => ({
|
| 471 |
-
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
| 472 |
-
}[c]));
|
| 473 |
-
}
|
| 474 |
-
|
| 475 |
-
// ---- Review modal ----
|
| 476 |
-
function openReviewModal() {
|
| 477 |
-
if (!currentUser) {
|
| 478 |
-
if (confirm('Please login to write a review. Go to login page?')) {
|
| 479 |
-
const id = getCurrentProductId();
|
| 480 |
-
window.location.href = `login.html?next=${encodeURIComponent('/product.html?id=' + id)}`;
|
| 481 |
-
}
|
| 482 |
-
return;
|
| 483 |
-
}
|
| 484 |
-
document.getElementById('reviewModal').classList.remove('hidden');
|
| 485 |
-
// Star picker
|
| 486 |
-
document.querySelectorAll('#starPicker button').forEach(btn => {
|
| 487 |
-
btn.onclick = () => {
|
| 488 |
-
const star = parseInt(btn.dataset.star);
|
| 489 |
-
document.getElementById('reviewRating').value = star;
|
| 490 |
-
document.querySelectorAll('#starPicker button').forEach(b => {
|
| 491 |
-
b.classList.remove('text-cyan-400');
|
| 492 |
-
b.classList.add('text-gray-300');
|
| 493 |
-
});
|
| 494 |
-
for (let i = 1; i <= star; i++) {
|
| 495 |
-
const b = document.querySelector(`#starPicker button[data-star="${i}"]`);
|
| 496 |
-
if (b) { b.classList.add('text-cyan-400'); b.classList.remove('text-gray-300'); }
|
| 497 |
-
}
|
| 498 |
-
};
|
| 499 |
-
});
|
| 500 |
-
document.getElementById('reviewForm').onsubmit = submitReview;
|
| 501 |
-
}
|
| 502 |
-
|
| 503 |
-
function closeReviewModal() {
|
| 504 |
-
document.getElementById('reviewModal').classList.add('hidden');
|
| 505 |
-
}
|
| 506 |
-
|
| 507 |
-
async function submitReview(e) {
|
| 508 |
-
e.preventDefault();
|
| 509 |
-
const errEl = document.getElementById('reviewError');
|
| 510 |
-
errEl.classList.add('hidden');
|
| 511 |
-
const rating = parseInt(document.getElementById('reviewRating').value);
|
| 512 |
-
if (!rating || rating < 1 || rating > 5) {
|
| 513 |
-
errEl.textContent = 'Please select a rating (1-5 stars)';
|
| 514 |
-
errEl.classList.remove('hidden');
|
| 515 |
-
return;
|
| 516 |
-
}
|
| 517 |
-
const r = await window.EdgeFunctions.reviews.create({
|
| 518 |
-
productId: Number(getCurrentProductId()),
|
| 519 |
-
rating,
|
| 520 |
-
title: document.getElementById('reviewTitle').value.trim(),
|
| 521 |
-
comment: document.getElementById('reviewComment').value.trim(),
|
| 522 |
-
});
|
| 523 |
-
if (r.success) {
|
| 524 |
-
closeReviewModal();
|
| 525 |
-
await loadProductReviews();
|
| 526 |
-
alert('Thanks for your review!');
|
| 527 |
-
} else {
|
| 528 |
-
errEl.textContent = r.error || 'Failed to submit review';
|
| 529 |
-
errEl.classList.remove('hidden');
|
| 530 |
-
}
|
| 531 |
-
}
|
| 532 |
-
|
| 533 |
-
async function markHelpful(reviewId) {
|
| 534 |
-
const r = await window.EdgeFunctions.reviews.helpful(reviewId);
|
| 535 |
-
if (r.success) await loadProductReviews();
|
| 536 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
link-account.html
DELETED
|
@@ -1,165 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Link WhatsApp · Cellex</title>
|
| 7 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 8 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
-
<link rel="stylesheet" href="css/common.css">
|
| 10 |
-
<script src="js/config/edge-functions.js?v=2"></script>
|
| 11 |
-
<style>
|
| 12 |
-
body { background: #f8fafc; }
|
| 13 |
-
.la-card {
|
| 14 |
-
background: #fff; border-radius: 16px;
|
| 15 |
-
box-shadow: 0 4px 14px rgba(0,0,0,0.06);
|
| 16 |
-
padding: 2rem; margin: 2rem auto; max-width: 560px;
|
| 17 |
-
}
|
| 18 |
-
.la-code-box {
|
| 19 |
-
background: linear-gradient(135deg, #00d4ff, #0891b2);
|
| 20 |
-
color: #0f172a; padding: 1.5rem; border-radius: 12px;
|
| 21 |
-
text-align: center; margin: 1.5rem 0;
|
| 22 |
-
}
|
| 23 |
-
.la-code-box .code {
|
| 24 |
-
font-size: 3rem; font-weight: 800; letter-spacing: 0.5rem;
|
| 25 |
-
font-family: 'Courier New', monospace;
|
| 26 |
-
}
|
| 27 |
-
.la-step {
|
| 28 |
-
display: flex; gap: 1rem; padding: 0.75rem 0;
|
| 29 |
-
border-bottom: 1px solid #e2e8f0;
|
| 30 |
-
}
|
| 31 |
-
.la-step:last-child { border-bottom: none; }
|
| 32 |
-
.la-step .num {
|
| 33 |
-
width: 32px; height: 32px; border-radius: 50%;
|
| 34 |
-
background: #cffafe; color: #0891b2;
|
| 35 |
-
display: flex; align-items: center; justify-content: center;
|
| 36 |
-
font-weight: 700; flex-shrink: 0;
|
| 37 |
-
}
|
| 38 |
-
</style>
|
| 39 |
-
</head>
|
| 40 |
-
<body>
|
| 41 |
-
<div id="nav-mount"></div>
|
| 42 |
-
<script src="js/load-navbar.js"></script>
|
| 43 |
-
|
| 44 |
-
<div class="la-card">
|
| 45 |
-
<h1 class="text-2xl font-bold mb-2">
|
| 46 |
-
<i class="fab fa-whatsapp text-green-500"></i> Link WhatsApp
|
| 47 |
-
</h1>
|
| 48 |
-
<p class="text-gray-600 mb-4">Connect your WhatsApp to your Cellex account so you can manage your cart, join group buys, and checkout — all from WhatsApp.</p>
|
| 49 |
-
|
| 50 |
-
<!-- Generate code form -->
|
| 51 |
-
<div id="generateSection">
|
| 52 |
-
<form id="phoneForm" class="space-y-3">
|
| 53 |
-
<div>
|
| 54 |
-
<label class="block text-sm font-medium mb-1">Your WhatsApp phone number</label>
|
| 55 |
-
<input type="tel" id="phone" placeholder="+234 801 234 5678" required
|
| 56 |
-
class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:border-amber-500">
|
| 57 |
-
<p class="text-xs text-gray-500 mt-1">Include country code (e.g. +234 for Nigeria).</p>
|
| 58 |
-
</div>
|
| 59 |
-
<button type="submit" class="w-full bg-gradient-to-r from-cyan-400 to-cyan-700 text-ink-900 font-bold py-2.5 rounded-lg hover:opacity-90">
|
| 60 |
-
Generate link code
|
| 61 |
-
</button>
|
| 62 |
-
</form>
|
| 63 |
-
</div>
|
| 64 |
-
|
| 65 |
-
<!-- Code display -->
|
| 66 |
-
<div id="codeSection" style="display:none;">
|
| 67 |
-
<div class="la-code-box">
|
| 68 |
-
<div class="text-xs font-bold uppercase tracking-wider mb-1">Your link code</div>
|
| 69 |
-
<div class="code" id="codeDisplay">—</div>
|
| 70 |
-
<div class="text-xs mt-1">Expires in 10 minutes</div>
|
| 71 |
-
</div>
|
| 72 |
-
<div class="space-y-2">
|
| 73 |
-
<div class="la-step">
|
| 74 |
-
<div class="num">1</div>
|
| 75 |
-
<div>Open WhatsApp and start a chat with the Cellex bot.</div>
|
| 76 |
-
</div>
|
| 77 |
-
<div class="la-step">
|
| 78 |
-
<div class="num">2</div>
|
| 79 |
-
<div>Send the message: <code class="bg-gray-100 px-1.5 py-0.5 rounded">link <strong id="codeInline">—</strong></code></div>
|
| 80 |
-
</div>
|
| 81 |
-
<div class="la-step">
|
| 82 |
-
<div class="num">3</div>
|
| 83 |
-
<div>The bot will confirm once your account is linked.</div>
|
| 84 |
-
</div>
|
| 85 |
-
</div>
|
| 86 |
-
<button onclick="resetForm()" class="mt-4 text-sm text-gray-500 hover:text-gray-700">
|
| 87 |
-
← Use a different phone number
|
| 88 |
-
</button>
|
| 89 |
-
</div>
|
| 90 |
-
|
| 91 |
-
<!-- Linked phones list -->
|
| 92 |
-
<div class="mt-6 pt-6 border-t">
|
| 93 |
-
<h2 class="font-bold text-lg mb-3">Linked phones</h2>
|
| 94 |
-
<div id="linksList" class="text-gray-500 text-sm">Loading…</div>
|
| 95 |
-
</div>
|
| 96 |
-
</div>
|
| 97 |
-
|
| 98 |
-
<script>
|
| 99 |
-
let currentUser = null;
|
| 100 |
-
(async () => {
|
| 101 |
-
const sess = await window.EdgeFunctions.auth.checkSession();
|
| 102 |
-
if (!sess.success || !sess.user) {
|
| 103 |
-
window.location.href = 'login.html?next=' + encodeURIComponent('/link-account.html');
|
| 104 |
-
return;
|
| 105 |
-
}
|
| 106 |
-
currentUser = sess.user;
|
| 107 |
-
await loadLinks();
|
| 108 |
-
|
| 109 |
-
document.getElementById('phoneForm').addEventListener('submit', handleGenerate);
|
| 110 |
-
})();
|
| 111 |
-
|
| 112 |
-
async function handleGenerate(e) {
|
| 113 |
-
e.preventDefault();
|
| 114 |
-
const phone = document.getElementById('phone').value.trim();
|
| 115 |
-
if (!phone) return;
|
| 116 |
-
const r = await window.EdgeFunctions.crossPlatform.generateLinkCode(phone);
|
| 117 |
-
if (r.success) {
|
| 118 |
-
document.getElementById('codeDisplay').textContent = r.code;
|
| 119 |
-
document.getElementById('codeInline').textContent = r.code;
|
| 120 |
-
document.getElementById('generateSection').style.display = 'none';
|
| 121 |
-
document.getElementById('codeSection').style.display = 'block';
|
| 122 |
-
} else {
|
| 123 |
-
alert('Failed: ' + (r.error || 'Unknown error'));
|
| 124 |
-
}
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
function resetForm() {
|
| 128 |
-
document.getElementById('phoneForm').reset();
|
| 129 |
-
document.getElementById('generateSection').style.display = 'block';
|
| 130 |
-
document.getElementById('codeSection').style.display = 'none';
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
async function loadLinks() {
|
| 134 |
-
const r = await window.EdgeFunctions.crossPlatform.myPhoneLinks();
|
| 135 |
-
const list = document.getElementById('linksList');
|
| 136 |
-
if (!r.success || !r.links || r.links.length === 0) {
|
| 137 |
-
list.innerHTML = '<div class="text-gray-500">No phones linked yet.</div>';
|
| 138 |
-
return;
|
| 139 |
-
}
|
| 140 |
-
list.innerHTML = r.links.map(l => `
|
| 141 |
-
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg mb-2">
|
| 142 |
-
<div>
|
| 143 |
-
<div class="font-medium">${escapeHtml(l.phone)}</div>
|
| 144 |
-
<div class="text-xs ${l.confirmed_at ? 'text-green-600' : 'text-cyan-600'}">
|
| 145 |
-
${l.confirmed_at ? '✓ Confirmed ' + new Date(l.confirmed_at).toLocaleDateString() : '⏳ Pending — code ' + l.link_code}
|
| 146 |
-
</div>
|
| 147 |
-
</div>
|
| 148 |
-
<button onclick="unlink('${escapeHtml(l.phone)}')" class="text-red-500 text-sm hover:bg-red-50 px-2 py-1 rounded">
|
| 149 |
-
<i class="fas fa-trash"></i>
|
| 150 |
-
</button>
|
| 151 |
-
</div>`).join('');
|
| 152 |
-
}
|
| 153 |
-
|
| 154 |
-
async function unlink(phone) {
|
| 155 |
-
if (!confirm('Unlink ' + phone + '?')) return;
|
| 156 |
-
const r = await window.EdgeFunctions.crossPlatform.unlinkPhone(phone);
|
| 157 |
-
if (r.success) await loadLinks();
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
function escapeHtml(s) {
|
| 161 |
-
return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
| 162 |
-
}
|
| 163 |
-
</script>
|
| 164 |
-
</body>
|
| 165 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|