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 CHANGED
@@ -1,9 +1,54 @@
1
- # Sensitive - don't commit
2
- .env
3
- .env.local
4
- *.secret
5
- __pycache__/
6
- *.pyc
7
- .git/
8
- supabase/.temp/
9
- supabase/supabase/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- FROM python:3.10-slim
 
2
 
3
- # Install system dependencies
4
- RUN apt-get update && apt-get install -y \
5
- build-essential \
6
- curl \
7
- && rm -rf /var/lib/apt/lists/*
8
 
9
  WORKDIR /app
10
 
11
- # Copy requirements first for caching
12
- COPY web-server/requirements.txt .
13
- RUN pip install --no-cache-dir -r requirements.txt
14
 
15
- # Copy everything else (frontend files + web-server)
16
  COPY . .
17
 
18
- # HF Spaces expects port 7860
 
 
 
 
 
 
 
 
 
19
  ENV PORT=7860
20
- EXPOSE 7860
 
 
 
 
 
21
 
22
- # Health check
23
- HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
24
- CMD curl -f http://localhost:7860/api/health || exit 1
25
 
26
- # Run the web server
27
- CMD ["python", "web-server/server.py"]
 
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>&copy; 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
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
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>&copy; 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>&copy; 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 (&euro;)</option>
322
- <option value="gbp">GBP (&pound;)</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>&copy; 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>&copy; 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: yellow
5
- colorTo: red
6
  sdk: docker
 
7
  pinned: false
8
- license: apache-2.0
9
- short_description: Cellex - Nigeria's #1 Marketplace
10
  ---
11
 
12
- # Cellex Web Server
13
 
14
- Serves the Cellex frontend (static files) + proxies `/api/*` requests to Supabase Edge Functions.
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  ## Architecture
17
 
18
- ```
19
- Browser → cellex-web.hf.space → web-server/server.py
20
- ├── /api/* → Supabase Edge Functions (with anon key from env)
21
- └── /* → static files (index.html, ai-chat.html, js/, etc.)
22
- ```
 
 
23
 
24
- The frontend has **ZERO** Supabase references — no URL, no keys, no SDK. All requests go through relative `/api/*` URLs.
25
 
26
- ## Environment Variables (set as Secrets)
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- | Key | Value |
29
- |-----|-------|
30
- | `SUPABASE_PROJECT_URL` | `https://tcwdbokruvlizkxcpkzj.supabase.co` |
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>&copy; 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
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
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>&copy; 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
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
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,'&quot;')}, 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">&times;</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
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
162
- }
163
- </script>
164
- </body>
165
- </html>