@refai.code/vite-boost 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/vite-boost +1313 -0
- package/package.json +16 -0
package/bin/vite-boost
ADDED
|
@@ -0,0 +1,1313 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
emulate -L zsh
|
|
3
|
+
set -u
|
|
4
|
+
|
|
5
|
+
# ==========================================
|
|
6
|
+
# 🚀 Vite Boost: Refai.Code Final Ultimate
|
|
7
|
+
# ==========================================
|
|
8
|
+
# Features:
|
|
9
|
+
# - Smart Router (Scroll/Hash/Auth)
|
|
10
|
+
# - Glassmorphism UI (Login/Signup with Socials)
|
|
11
|
+
# - Real JSON Server Auth (Users save to db.json)
|
|
12
|
+
# - Concurrent Run (Vite + JSON Server)
|
|
13
|
+
|
|
14
|
+
echo "\n🚀 \033[1;36mVite Boost: Refai.Code Final Edition\033[0m\n"
|
|
15
|
+
|
|
16
|
+
# --- Validation: Check if it's a Vite project ---
|
|
17
|
+
if [ ! -f "package.json" ]; then
|
|
18
|
+
echo "❌ Error: package.json not found. Are you in the right directory?"
|
|
19
|
+
return 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Check if Vite is in dependencies or devDependencies
|
|
23
|
+
if ! grep -q '"vite"' package.json; then
|
|
24
|
+
echo "⚠️ Warning: Vite not found in package.json."
|
|
25
|
+
if read -q "choice?This doesn't look like a Vite project. Continue anyway? (y/N) "; then
|
|
26
|
+
echo ""
|
|
27
|
+
else
|
|
28
|
+
echo "\n❌ Setup cancelled."
|
|
29
|
+
return 1
|
|
30
|
+
fi
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
echo "✅ Detected Vite project. Proceeding...\n"
|
|
34
|
+
|
|
35
|
+
# --- 1. Interactive Prompts ---
|
|
36
|
+
ask() {
|
|
37
|
+
if read -q "choice?📦 Install $1? (y/N) "; then
|
|
38
|
+
echo "\n ✅ Selected $1"
|
|
39
|
+
eval "$2=true"
|
|
40
|
+
else
|
|
41
|
+
echo "\n ❌ Skipped $1"
|
|
42
|
+
eval "$2=false"
|
|
43
|
+
fi
|
|
44
|
+
echo ""
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Helper: Check file overwrite
|
|
48
|
+
check_overwrite() {
|
|
49
|
+
local file=$1
|
|
50
|
+
if [ -f "$file" ]; then
|
|
51
|
+
if read -q "choice?⚠️ $file already exists. Overwrite? (y/N) "; then
|
|
52
|
+
echo ""
|
|
53
|
+
return 0 # Yes, overwrite
|
|
54
|
+
else
|
|
55
|
+
echo "\n ⏭️ Skipped $file"
|
|
56
|
+
return 1 # No, skip
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
return 0 # File doesn't exist, proceed
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Defaults
|
|
63
|
+
tail=false
|
|
64
|
+
expr=false
|
|
65
|
+
pwa=false
|
|
66
|
+
axio=false
|
|
67
|
+
json=false
|
|
68
|
+
|
|
69
|
+
# Track what was installed
|
|
70
|
+
installed_packages=()
|
|
71
|
+
|
|
72
|
+
ask "TailwindCSS" tail
|
|
73
|
+
ask "Axios (HTTP Client)" axio
|
|
74
|
+
ask "PWA Support" pwa
|
|
75
|
+
ask "JSON Server (Real Database & Auth)" json
|
|
76
|
+
|
|
77
|
+
# ==========================================
|
|
78
|
+
# 🛠️ Architecture Setup
|
|
79
|
+
# ==========================================
|
|
80
|
+
echo "\n📁 Creating project structure..."
|
|
81
|
+
mkdir -p src/{components,pages,router,styles,services,utils,assets}
|
|
82
|
+
|
|
83
|
+
# -----------------------------
|
|
84
|
+
# 1. Utilities
|
|
85
|
+
# -----------------------------
|
|
86
|
+
echo "\n🔧 Generating utility files..."
|
|
87
|
+
if check_overwrite "src/utils/helpers.js"; then
|
|
88
|
+
cat > src/utils/helpers.js <<'EOF'
|
|
89
|
+
export const sanitize = (str) => {
|
|
90
|
+
if (!str) return '';
|
|
91
|
+
const div = document.createElement('div');
|
|
92
|
+
div.textContent = str;
|
|
93
|
+
return div.innerHTML;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const renderStars = (rating) => {
|
|
97
|
+
const stars = Math.round(rating);
|
|
98
|
+
let html = '';
|
|
99
|
+
for (let i = 1; i <= 5; i++) {
|
|
100
|
+
html += `<span style="color: ${i <= stars ? '#f59e0b' : '#d1d5db'}">★</span>`;
|
|
101
|
+
}
|
|
102
|
+
return html;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
106
|
+
EOF
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
cat > src/utils/toast.js <<'EOF'
|
|
110
|
+
export const toast = {
|
|
111
|
+
show: (msg, type = 'error') => {
|
|
112
|
+
const el = document.createElement('div');
|
|
113
|
+
const color = type === 'success' ? '#10b981' : '#ef4444';
|
|
114
|
+
el.style.cssText = `
|
|
115
|
+
position: fixed; top: 20px; right: 20px;
|
|
116
|
+
background: ${color}; color: white; padding: 1rem 1.5rem;
|
|
117
|
+
border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
118
|
+
z-index: 9999; animation: slideIn 0.3s ease-out; font-weight: 500;
|
|
119
|
+
`;
|
|
120
|
+
el.textContent = msg;
|
|
121
|
+
document.body.appendChild(el);
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
el.style.opacity = '0';
|
|
124
|
+
el.style.transform = 'translateY(-20px)';
|
|
125
|
+
el.style.transition = 'all 0.3s';
|
|
126
|
+
setTimeout(() => el.remove(), 300);
|
|
127
|
+
}, 3000);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
EOF
|
|
131
|
+
|
|
132
|
+
# -----------------------------
|
|
133
|
+
# 2. API & Auth Services
|
|
134
|
+
# -----------------------------
|
|
135
|
+
# Dynamic API Service
|
|
136
|
+
if [ "$axio" = true ]; then
|
|
137
|
+
cat > src/services/api.js <<'EOF'
|
|
138
|
+
import axios from 'axios';
|
|
139
|
+
import { toast } from '../utils/toast.js';
|
|
140
|
+
|
|
141
|
+
const API_URL = 'http://localhost:3000';
|
|
142
|
+
|
|
143
|
+
const handle = async (promise) => {
|
|
144
|
+
try {
|
|
145
|
+
const res = await promise;
|
|
146
|
+
return res.data;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error(err);
|
|
149
|
+
toast.show(err.response?.data?.message || err.message || 'API Error', 'error');
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const api = {
|
|
155
|
+
get: (url) => handle(axios.get(`${API_URL}${url}`)),
|
|
156
|
+
post: (url, data) => handle(axios.post(`${API_URL}${url}`, data)),
|
|
157
|
+
put: (url, data) => handle(axios.put(`${API_URL}${url}`, data)),
|
|
158
|
+
delete: (url) => handle(axios.delete(`${API_URL}${url}`))
|
|
159
|
+
};
|
|
160
|
+
EOF
|
|
161
|
+
else
|
|
162
|
+
cat > src/services/api.js <<'EOF'
|
|
163
|
+
import { toast } from '../utils/toast.js';
|
|
164
|
+
|
|
165
|
+
const API_URL = 'http://localhost:3000';
|
|
166
|
+
|
|
167
|
+
const request = async (url, options = {}) => {
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch(`${API_URL}${url}`, {
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
...options,
|
|
172
|
+
});
|
|
173
|
+
if (!res.ok) throw new Error(`API Error: ${res.statusText}`);
|
|
174
|
+
return await res.json();
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.error(err);
|
|
177
|
+
toast.show(err.message, 'error');
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const api = {
|
|
183
|
+
get: (url) => request(url),
|
|
184
|
+
post: (url, data) => request(url, { method: 'POST', body: JSON.stringify(data) }),
|
|
185
|
+
delete: (url) => request(url, { method: 'DELETE' }),
|
|
186
|
+
put: (url, data) => request(url, { method: 'PUT', body: JSON.stringify(data) })
|
|
187
|
+
};
|
|
188
|
+
EOF
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
# Auth Service with persistent state
|
|
192
|
+
cat > src/services/auth.js <<'EOF'
|
|
193
|
+
|
|
194
|
+
import { api } from './api.js';
|
|
195
|
+
import { toast } from '../utils/toast.js';
|
|
196
|
+
|
|
197
|
+
export const authState = {
|
|
198
|
+
user: JSON.parse(localStorage.getItem('user')) || null,
|
|
199
|
+
listeners: [],
|
|
200
|
+
|
|
201
|
+
subscribe(fn) { this.listeners.push(fn); },
|
|
202
|
+
notify() { this.listeners.forEach(fn => fn(this.user)); },
|
|
203
|
+
|
|
204
|
+
async login(email, password) {
|
|
205
|
+
try {
|
|
206
|
+
// For JSON Server, we simulate login by fetching users
|
|
207
|
+
const users = await api.get(`/users?email=${email}`);
|
|
208
|
+
const user = users.find(u => u.password === password);
|
|
209
|
+
|
|
210
|
+
if (!user) {
|
|
211
|
+
throw new Error('Invalid credentials');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.user = user;
|
|
215
|
+
localStorage.setItem('user', JSON.stringify(user));
|
|
216
|
+
this.notify();
|
|
217
|
+
toast.show(`Welcome back, ${user.firstName}!`, 'success');
|
|
218
|
+
return true;
|
|
219
|
+
} catch (err) {
|
|
220
|
+
toast.show('Login failed: Invalid email or password');
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
async signup(data) {
|
|
226
|
+
try {
|
|
227
|
+
// Check if user exists
|
|
228
|
+
const existing = await api.get(`/users?email=${data.email}`);
|
|
229
|
+
if (existing.length > 0) throw new Error('Email already registered');
|
|
230
|
+
|
|
231
|
+
const newUser = {
|
|
232
|
+
...data,
|
|
233
|
+
avatar: `https://ui-avatars.com/api/?name=${data.firstName}+${data.lastName}&background=4f46e5&color=fff`
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
await api.post('/users', newUser);
|
|
237
|
+
toast.show('Account created! Please login.', 'success');
|
|
238
|
+
return true;
|
|
239
|
+
} catch (err) {
|
|
240
|
+
toast.show(err.message);
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
// Edit profile method
|
|
246
|
+
async updateProfile(updatedData) {
|
|
247
|
+
try {
|
|
248
|
+
if (!this.user) throw new Error('No user logged in');
|
|
249
|
+
|
|
250
|
+
const updatedUser = { ...this.user, ...updatedData };
|
|
251
|
+
await api.put(`/users/${this.user.id}`, updatedUser);
|
|
252
|
+
|
|
253
|
+
this.user = updatedUser;
|
|
254
|
+
localStorage.setItem('user', JSON.stringify(updatedUser));
|
|
255
|
+
this.notify();
|
|
256
|
+
toast.show('Profile updated successfully', 'success');
|
|
257
|
+
return true;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
toast.show('Profile update failed: ' + err.message);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
logout() {
|
|
265
|
+
this.user = null;
|
|
266
|
+
localStorage.removeItem('user');
|
|
267
|
+
this.notify();
|
|
268
|
+
toast.show('Logged out successfully', 'success');
|
|
269
|
+
window.location.href = '/';
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
EOF
|
|
273
|
+
|
|
274
|
+
# -----------------------------
|
|
275
|
+
# 3. Router
|
|
276
|
+
# -----------------------------
|
|
277
|
+
cat > src/router/LiteRouter.js <<'EOF'
|
|
278
|
+
export class LiteRouter {
|
|
279
|
+
constructor(routes, appElement) {
|
|
280
|
+
this.routes = routes;
|
|
281
|
+
this.app = document.querySelector(appElement);
|
|
282
|
+
|
|
283
|
+
window.addEventListener('popstate', () => this.loadRoute());
|
|
284
|
+
|
|
285
|
+
document.body.addEventListener('click', e => {
|
|
286
|
+
const anchor = e.target.closest('a[href^="#"]');
|
|
287
|
+
if (anchor) {
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
const id = anchor.getAttribute('href');
|
|
290
|
+
if (window.location.pathname !== '/') {
|
|
291
|
+
this.navigateTo('/' + id);
|
|
292
|
+
} else {
|
|
293
|
+
this.scrollToId(id);
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const link = e.target.closest('[data-link]');
|
|
299
|
+
if (link) {
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
this.navigateTo(link.getAttribute('href'));
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Initial Load Logic
|
|
306
|
+
window.addEventListener('load', () => {
|
|
307
|
+
this.loadRoute();
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
navigateTo(url) {
|
|
312
|
+
history.pushState(null, null, url);
|
|
313
|
+
this.loadRoute();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async loadRoute() {
|
|
317
|
+
let path = window.location.pathname;
|
|
318
|
+
|
|
319
|
+
// Check hash intent from other pages
|
|
320
|
+
let hashTarget = '';
|
|
321
|
+
if (path.includes('/#')) {
|
|
322
|
+
const parts = path.split('/#');
|
|
323
|
+
path = '/';
|
|
324
|
+
hashTarget = '#' + parts[1];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (path === '/index.html') path = '/';
|
|
328
|
+
|
|
329
|
+
const match = this.routes.find(r => {
|
|
330
|
+
const rx = new RegExp("^" + r.path.replace(/:(\w+)/g, "(?<$1>[^/]+)") + "$");
|
|
331
|
+
return path.match(rx);
|
|
332
|
+
}) || this.routes.find(r => r.path === '*');
|
|
333
|
+
|
|
334
|
+
const result = path.match(new RegExp("^" + match.path.replace(/:(\w+)/g, "(?<$1>[^/]+)") + "$"));
|
|
335
|
+
const params = result?.groups || {};
|
|
336
|
+
const search = new URLSearchParams(window.location.search);
|
|
337
|
+
|
|
338
|
+
// Layout
|
|
339
|
+
if (match.layout) {
|
|
340
|
+
if (!this.currentLayout || this.currentLayout !== match.layout) {
|
|
341
|
+
this.app.innerHTML = await match.layout();
|
|
342
|
+
this.currentLayout = match.layout;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const outlet = this.app.querySelector('[data-outlet]') || this.app;
|
|
347
|
+
|
|
348
|
+
// Spinner
|
|
349
|
+
outlet.innerHTML = '<div class="spinner-container"><div class="spinner"></div></div>';
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const view = await match.component({ params, query: search });
|
|
353
|
+
outlet.innerHTML = view;
|
|
354
|
+
} catch (err) {
|
|
355
|
+
outlet.innerHTML = `<div class="error-page"><h2>Error</h2><p>${err.message}</p></div>`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (match.title) document.title = match.title;
|
|
359
|
+
|
|
360
|
+
// Scroll Handling
|
|
361
|
+
if (hashTarget) {
|
|
362
|
+
setTimeout(() => this.scrollToId(hashTarget), 300);
|
|
363
|
+
} else if (!window.location.hash) {
|
|
364
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
scrollToId(id) {
|
|
369
|
+
const el = document.querySelector(id);
|
|
370
|
+
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
init() {
|
|
374
|
+
// defer init call to window.load often safest or immediate
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
EOF
|
|
378
|
+
|
|
379
|
+
# -----------------------------
|
|
380
|
+
# 4. Styles (Glass, Spinner, Cards)
|
|
381
|
+
# -----------------------------
|
|
382
|
+
cat > src/styles/main.css <<'EOF'
|
|
383
|
+
:root {
|
|
384
|
+
--primary: #4f46e5;
|
|
385
|
+
--bg: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
386
|
+
--text: #1f2937;
|
|
387
|
+
--glass: rgba(255, 255, 255, 0.85);
|
|
388
|
+
--glass-border: rgba(255, 255, 255, 0.4);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
* { box-sizing: border-box; }
|
|
392
|
+
html { scroll-behavior: smooth; }
|
|
393
|
+
body { margin:0; font-family: 'Outfit', sans-serif; background: var(--bg); min-height: 100vh; color: var(--text); }
|
|
394
|
+
html, body { overflow-x: hidden; }
|
|
395
|
+
|
|
396
|
+
/* Keyframes */
|
|
397
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
398
|
+
@keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
399
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
400
|
+
|
|
401
|
+
/* Utils */
|
|
402
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; }
|
|
403
|
+
.spinner-container { display: flex; justify-content: center; padding: 4rem; width: 100%; }
|
|
404
|
+
.spinner { width: 40px; height: 40px; border: 4px solid #ddd; border-top-color: var(--primary); border-radius: 50%; animation: spin 0.8s linear infinite; }
|
|
405
|
+
|
|
406
|
+
/* Buttons */
|
|
407
|
+
.btn {
|
|
408
|
+
padding: 0.75rem 1.5rem; border-radius: 8px; border: none; font-weight: 600; cursor: pointer;
|
|
409
|
+
transition: all 0.2s; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
|
|
410
|
+
}
|
|
411
|
+
.btn-primary {
|
|
412
|
+
background: var(--primary);
|
|
413
|
+
color: white;
|
|
414
|
+
box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.3);
|
|
415
|
+
}
|
|
416
|
+
.btn-primary:hover {
|
|
417
|
+
transform: translateY(-2px);
|
|
418
|
+
box-shadow: 0 6px 15px -3px rgba(79, 70, 229, 0.4);
|
|
419
|
+
}
|
|
420
|
+
.btn-social {
|
|
421
|
+
background: white;
|
|
422
|
+
border: 1px solid #e5e7eb; color: #374151;
|
|
423
|
+
width: 100%; font-weight: 500;}
|
|
424
|
+
.btn-social:hover {
|
|
425
|
+
background: #f9fafb;
|
|
426
|
+
border-color: #d1d5db;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Glass Card */
|
|
430
|
+
.glass-panel {
|
|
431
|
+
background: var(--glass);
|
|
432
|
+
backdrop-filter: blur(12px);
|
|
433
|
+
border: 1px solid var(--glass-border);
|
|
434
|
+
border-radius: 16px;
|
|
435
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
|
|
436
|
+
padding: 2.5rem;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Auth Specific */
|
|
440
|
+
.auth-container {
|
|
441
|
+
display: flex;
|
|
442
|
+
justify-content: center;
|
|
443
|
+
align-items: center;
|
|
444
|
+
min-height: 80vh;
|
|
445
|
+
}
|
|
446
|
+
.auth-box {
|
|
447
|
+
width: 100%; max-width: 420px;
|
|
448
|
+
}
|
|
449
|
+
input {
|
|
450
|
+
width: 100%;
|
|
451
|
+
padding: 0.8rem 1rem;
|
|
452
|
+
margin-bottom: 1rem;
|
|
453
|
+
border: 1px solid #d1d5db;
|
|
454
|
+
border-radius: 8px;
|
|
455
|
+
font-size: 1rem;
|
|
456
|
+
transition: border 0.2s;
|
|
457
|
+
}
|
|
458
|
+
input:focus {
|
|
459
|
+
outline: none;
|
|
460
|
+
border-color: var(--primary);
|
|
461
|
+
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1); }
|
|
462
|
+
|
|
463
|
+
/* Navbar Mobile Responsive */
|
|
464
|
+
.navbar {
|
|
465
|
+
position: sticky;
|
|
466
|
+
top: 0;
|
|
467
|
+
z-index: 1000;
|
|
468
|
+
background: rgba(255,255,255,0.95);
|
|
469
|
+
backdrop-filter: blur(10px);
|
|
470
|
+
border-bottom: 1px solid rgba(0,0,0,0.05);
|
|
471
|
+
padding: 0.5rem 0;
|
|
472
|
+
}
|
|
473
|
+
.nav-inner {
|
|
474
|
+
max-width: 1200px;
|
|
475
|
+
margin:0 auto;
|
|
476
|
+
padding: 0 1.5rem;
|
|
477
|
+
display: flex;
|
|
478
|
+
justify-content: space-between;
|
|
479
|
+
align-items: center;
|
|
480
|
+
height: 60px;
|
|
481
|
+
}
|
|
482
|
+
.nav-links {
|
|
483
|
+
display: flex;
|
|
484
|
+
align-items: center;
|
|
485
|
+
gap: 2rem;
|
|
486
|
+
}
|
|
487
|
+
.nav-links a {
|
|
488
|
+
text-decoration: none;
|
|
489
|
+
color: var(--text);
|
|
490
|
+
font-weight: 500;
|
|
491
|
+
padding: 0.5rem 1rem;
|
|
492
|
+
border-radius: 8px;
|
|
493
|
+
transition: all 0.2s;
|
|
494
|
+
}
|
|
495
|
+
.nav-links a:hover {
|
|
496
|
+
color: var(--primary);
|
|
497
|
+
background: rgba(0,0,0,0.05);
|
|
498
|
+
}
|
|
499
|
+
.nav-links a.active {
|
|
500
|
+
color: white;
|
|
501
|
+
background: var(--primary);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* Hamburger */
|
|
505
|
+
.hamburger {
|
|
506
|
+
display: none;
|
|
507
|
+
font-size: 1.5rem;
|
|
508
|
+
cursor: pointer;
|
|
509
|
+
background:none;
|
|
510
|
+
border:none; color:var(--text); }
|
|
511
|
+
|
|
512
|
+
/* Mobile Menu */
|
|
513
|
+
@media (max-width: 768px) {
|
|
514
|
+
.nav-links {
|
|
515
|
+
display: none;
|
|
516
|
+
position: absolute;
|
|
517
|
+
top: 60px;
|
|
518
|
+
left: 0;
|
|
519
|
+
width: 100%;
|
|
520
|
+
background: white;
|
|
521
|
+
flex-direction: column;
|
|
522
|
+
padding: 1rem 0;
|
|
523
|
+
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
|
|
524
|
+
border-top: 1px solid #eee;
|
|
525
|
+
}
|
|
526
|
+
.nav-links.active {
|
|
527
|
+
display: flex;
|
|
528
|
+
}
|
|
529
|
+
.nav-links a {
|
|
530
|
+
margin: 0; padding: 1rem;
|
|
531
|
+
width: 100%; text-align: center;
|
|
532
|
+
}
|
|
533
|
+
.nav-links div {
|
|
534
|
+
border:none !important;
|
|
535
|
+
padding: 1rem 0 !important;
|
|
536
|
+
margin:0 !important;
|
|
537
|
+
width: 100%;
|
|
538
|
+
display: flex;
|
|
539
|
+
justify-content: center;
|
|
540
|
+
}
|
|
541
|
+
.hamburger {
|
|
542
|
+
display: block;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* Modal Overlay System */
|
|
547
|
+
.modal-overlay {
|
|
548
|
+
position: fixed; inset: 0; background: rgba(0,0,0,0.6); backdrop-filter: blur(4px);
|
|
549
|
+
display: flex; justify-content: center; align-items: center; z-index: 2000;
|
|
550
|
+
animation: fadeIn 0.2s ease-out;
|
|
551
|
+
}
|
|
552
|
+
.modal-content {
|
|
553
|
+
background: white; width: 100%; max-width: 450px; border-radius: 16px; padding: 2rem;
|
|
554
|
+
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
|
|
555
|
+
position: relative; animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
556
|
+
}
|
|
557
|
+
.close-modal {
|
|
558
|
+
position: absolute; top: 1rem; right: 1rem; background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #999;
|
|
559
|
+
}
|
|
560
|
+
@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
561
|
+
|
|
562
|
+
.grid {
|
|
563
|
+
display: grid;
|
|
564
|
+
grid-template-columns: repeat(auto-fill,
|
|
565
|
+
minmax(280px, 1fr));
|
|
566
|
+
gap: 2rem;
|
|
567
|
+
}
|
|
568
|
+
.card {
|
|
569
|
+
background: white;
|
|
570
|
+
border-radius: 12px;
|
|
571
|
+
overflow: hidden;
|
|
572
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
573
|
+
transition: transform 0.2s;
|
|
574
|
+
display: flex;
|
|
575
|
+
flex-direction: column;
|
|
576
|
+
}
|
|
577
|
+
.card:hover {
|
|
578
|
+
transform: translateY(-5px);
|
|
579
|
+
box-shadow: 0 12px 20px -5px rgba(0,0,0,0.1); }
|
|
580
|
+
.card-img {
|
|
581
|
+
width: 100%;
|
|
582
|
+
height: 220px;
|
|
583
|
+
object-fit: cover;
|
|
584
|
+
}
|
|
585
|
+
.card-body {
|
|
586
|
+
padding: 1.5rem;
|
|
587
|
+
flex: 1; display:
|
|
588
|
+
flex; flex-direction: column;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/* Product Detail Page Responsive */
|
|
592
|
+
@media (max-width: 768px) {
|
|
593
|
+
.glass-panel {
|
|
594
|
+
grid-template-columns: 1fr !important;
|
|
595
|
+
padding: 1.5rem !important;
|
|
596
|
+
}
|
|
597
|
+
.product-detail-img {
|
|
598
|
+
margin-bottom: 1.5rem;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
EOF
|
|
602
|
+
|
|
603
|
+
# -----------------------------
|
|
604
|
+
# 5. Components
|
|
605
|
+
# -----------------------------
|
|
606
|
+
cat > src/components/Navbar.js <<'EOF'
|
|
607
|
+
import { authState } from '../services/auth.js';
|
|
608
|
+
import { openLoginModal, openProfileModal, openSignupModal } from '../utils/modals.js';
|
|
609
|
+
|
|
610
|
+
export const Navbar = () => {
|
|
611
|
+
const user = authState.user;
|
|
612
|
+
|
|
613
|
+
// Toggle Mobile Menu Logic
|
|
614
|
+
setTimeout(() => {
|
|
615
|
+
const btn = document.getElementById('hamburger-btn');
|
|
616
|
+
const menu = document.getElementById('nav-menu');
|
|
617
|
+
if(btn && menu) {
|
|
618
|
+
btn.onclick = () => menu.classList.toggle('active');
|
|
619
|
+
// Close menu when clicking any link
|
|
620
|
+
menu.querySelectorAll('a, button').forEach(el => {
|
|
621
|
+
el.addEventListener('click', () => menu.classList.remove('active'));
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}, 0);
|
|
625
|
+
|
|
626
|
+
return `<nav class="navbar">
|
|
627
|
+
<div class="nav-inner">
|
|
628
|
+
<a href="/" class="logo" style="font-weight:800; font-size:1.4rem; text-decoration:none; color:var(--text)" data-link>
|
|
629
|
+
Refai<span style="color:var(--primary)">.Code</span>
|
|
630
|
+
</a>
|
|
631
|
+
|
|
632
|
+
<button class="hamburger" id="hamburger-btn">☰</button>
|
|
633
|
+
|
|
634
|
+
<div class="nav-links" id="nav-menu">
|
|
635
|
+
<a href="/" data-link>Home</a>
|
|
636
|
+
<a href="#about">About</a>
|
|
637
|
+
<a href="/products" data-link>Products</a>
|
|
638
|
+
|
|
639
|
+
<div style="margin-left: 2rem; padding-left: 2rem; border-left: 1px solid #ddd;">
|
|
640
|
+
${user ? `
|
|
641
|
+
<div style="display:flex; align-items:center; gap:0.6rem; cursor:pointer;" onclick="window.openProfile()">
|
|
642
|
+
<img src="${user.avatar}" style="width:34px; height:34px; border-radius:50%; border:2px solid var(--primary);" alt="Avatar">
|
|
643
|
+
<span style="font-weight:600; font-size:0.9rem;">${user.firstName}</span>
|
|
644
|
+
</div>
|
|
645
|
+
` : `
|
|
646
|
+
<button class="btn btn-primary" onclick="window.openLogin()" style="padding:0.4rem 1.2rem; font-size:0.9rem;">Sign In</button>
|
|
647
|
+
`}
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
</nav>
|
|
652
|
+
`;
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// Global handlers for Modals
|
|
656
|
+
window.openLogin = openLoginModal;
|
|
657
|
+
window.openProfile = openProfileModal;
|
|
658
|
+
window.openSignup = openSignupModal;
|
|
659
|
+
EOF
|
|
660
|
+
|
|
661
|
+
# --- Modal Manager (New File) ---
|
|
662
|
+
cat > src/utils/modals.js <<'EOF'
|
|
663
|
+
import { authState } from '../services/auth.js';
|
|
664
|
+
import { toast } from './toast.js';
|
|
665
|
+
|
|
666
|
+
const createOverlay = (content) => {
|
|
667
|
+
const existing = document.querySelector('.modal-overlay');
|
|
668
|
+
if(existing) existing.remove();
|
|
669
|
+
|
|
670
|
+
const overlay = document.createElement('div');
|
|
671
|
+
overlay.className = 'modal-overlay';
|
|
672
|
+
overlay.innerHTML = `
|
|
673
|
+
<div class="modal-content">
|
|
674
|
+
<button class="close-modal" onclick="this.closest('.modal-overlay').remove()">×</button>
|
|
675
|
+
${content}
|
|
676
|
+
</div>
|
|
677
|
+
`;
|
|
678
|
+
overlay.addEventListener('click', (e) => {
|
|
679
|
+
if(e.target === overlay) overlay.remove();
|
|
680
|
+
});
|
|
681
|
+
document.body.appendChild(overlay);
|
|
682
|
+
return overlay;
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
export const openLoginModal = () => {
|
|
686
|
+
createOverlay(`
|
|
687
|
+
<h2 style="text-align:center; margin-bottom:1rem;">Welcome Back</h2>
|
|
688
|
+
<p style="text-align:center; color:#6b7280; margin-bottom:1.5rem; font-size:0.9rem;">Sign in to your account</p>
|
|
689
|
+
|
|
690
|
+
<form onsubmit="window.handleLogin(event)">
|
|
691
|
+
<input type="email" name="email" value="admin@refai.code" placeholder="Email Address" required>
|
|
692
|
+
<input type="password" name="password" value="admin123" placeholder="Password" required>
|
|
693
|
+
<button type="submit" class="btn btn-primary" style="width:100%; margin-bottom:1rem;">Sign In</button>
|
|
694
|
+
</form>
|
|
695
|
+
|
|
696
|
+
<div style="display:flex; align-items:center; margin:1.5rem 0; color:#9ca3af; font-size:0.85rem;">
|
|
697
|
+
<div style="flex:1; height:1px; background:#e5e7eb;"></div>
|
|
698
|
+
<span style="padding:0 1rem;">OR CONTINUE WITH</span>
|
|
699
|
+
<div style="flex:1; height:1px; background:#e5e7eb;"></div>
|
|
700
|
+
</div>
|
|
701
|
+
|
|
702
|
+
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.8rem; margin-bottom:1rem;">
|
|
703
|
+
<button class="btn btn-social" onclick="toast.show('Google Auth Coming Soon', 'error')">
|
|
704
|
+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12.61C5,9.5 7.81,5.7 12.19,5.7C13.63,5.7 15.71,6.5 16.55,7.34L19.07,4.8C16.8,2.77 14.28,2.15 12.18,2.15C5.83,2.15 1.15,7.39 1.15,12.61C1.15,17.97 4.96,22.85 12.18,22.85C18.27,22.85 22.85,17.7 22.85,12.61C22.85,11.75 22.75,11.53 22.65,11.1H21.35Z"/></svg> Google
|
|
705
|
+
</button>
|
|
706
|
+
<button class="btn btn-social" onclick="toast.show('GitHub Auth Coming Soon', 'error')">
|
|
707
|
+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z"/></svg> GitHub
|
|
708
|
+
</button>
|
|
709
|
+
</div>
|
|
710
|
+
|
|
711
|
+
<p style="text-align:center; font-size:0.85rem; color:#6b7280;">
|
|
712
|
+
Don't have an account? <a href="#" onclick="event.preventDefault(); window.openSignup()" style="color:var(--primary); font-weight:600; text-decoration:none;">Sign Up</a>
|
|
713
|
+
</p>
|
|
714
|
+
`);
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
export const openSignupModal = () => {
|
|
718
|
+
createOverlay(`
|
|
719
|
+
<h2 style="text-align:center; margin-bottom:1rem;">Create Account</h2>
|
|
720
|
+
<p style="text-align:center; color:#6b7280; margin-bottom:1.5rem; font-size:0.9rem;">Join Refai.Code today</p>
|
|
721
|
+
|
|
722
|
+
<form onsubmit="window.handleSignup(event)">
|
|
723
|
+
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.5rem;">
|
|
724
|
+
<input type="text" name="firstName" placeholder="First Name" required>
|
|
725
|
+
<input type="text" name="lastName" placeholder="Last Name" required>
|
|
726
|
+
</div>
|
|
727
|
+
<input type="email" name="email" placeholder="Email Address" required>
|
|
728
|
+
<input type="password" name="password" placeholder="Create Password" required>
|
|
729
|
+
<button type="submit" class="btn btn-primary" style="width:100%; margin-bottom:1rem;">Create Account</button>
|
|
730
|
+
</form>
|
|
731
|
+
|
|
732
|
+
<div style="display:flex; align-items:center; margin:1.5rem 0; color:#9ca3af; font-size:0.85rem;">
|
|
733
|
+
<div style="flex:1; height:1px; background:#e5e7eb;"></div>
|
|
734
|
+
<span style="padding:0 1rem;">OR SIGN UP WITH</span>
|
|
735
|
+
<div style="flex:1; height:1px; background:#e5e7eb;"></div>
|
|
736
|
+
</div>
|
|
737
|
+
|
|
738
|
+
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.8rem; margin-bottom:1rem;">
|
|
739
|
+
<button class="btn btn-social" onclick="toast.show('Google Auth Coming Soon', 'error')">
|
|
740
|
+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12.61C5,9.5 7.81,5.7 12.19,5.7C13.63,5.7 15.71,6.5 16.55,7.34L19.07,4.8C16.8,2.77 14.28,2.15 12.18,2.15C5.83,2.15 1.15,7.39 1.15,12.61C1.15,17.97 4.96,22.85 12.18,22.85C18.27,22.85 22.85,17.7 22.85,12.61C22.85,11.75 22.75,11.53 22.65,11.1H21.35Z"/></svg> Google
|
|
741
|
+
</button>
|
|
742
|
+
<button class="btn btn-social" onclick="toast.show('GitHub Auth Coming Soon', 'error')">
|
|
743
|
+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z"/></svg> GitHub
|
|
744
|
+
</button>
|
|
745
|
+
</div>
|
|
746
|
+
|
|
747
|
+
<p style="text-align:center; font-size:0.85rem; color:#6b7280;">
|
|
748
|
+
Already have an account? <a href="#" onclick="event.preventDefault(); window.openLogin()" style="color:var(--primary); font-weight:600; text-decoration:none;">Log In</a>
|
|
749
|
+
</p>
|
|
750
|
+
`);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
export const openProfileModal = () => {
|
|
754
|
+
const u = authState.user;
|
|
755
|
+
if(!u) return;
|
|
756
|
+
|
|
757
|
+
createOverlay(`
|
|
758
|
+
<div style="text-align:center; margin-bottom:1.5rem;">
|
|
759
|
+
<img src="${u.avatar}" style="width:80px; height:80px; border-radius:50%; margin-bottom:1rem; border:3px solid var(--primary);">
|
|
760
|
+
<h3>Edit Profile</h3>
|
|
761
|
+
</div>
|
|
762
|
+
<form onsubmit="window.handleUpdateProfile(event)">
|
|
763
|
+
<div style="display:flex; gap:0.5rem;">
|
|
764
|
+
<input type="text" name="firstName" value="${u.firstName}" placeholder="First Name">
|
|
765
|
+
<input type="text" name="lastName" value="${u.lastName}" placeholder="Last Name">
|
|
766
|
+
</div>
|
|
767
|
+
<input type="email" name="email" value="${u.email}" readonly style="background:#f3f4f6; color:#999;" title="Email cannot be changed">
|
|
768
|
+
<input type="password" name="password" placeholder="New Password (Optional)">
|
|
769
|
+
|
|
770
|
+
<div style="display:flex; gap:1rem; margin-top:1rem;">
|
|
771
|
+
<button type="button" class="btn" style="flex:1; background:#fee; color:red;" onclick="window.auth.logout()">Logout</button>
|
|
772
|
+
<button type="submit" class="btn btn-primary" style="flex:1;">Update Profile</button>
|
|
773
|
+
</div>
|
|
774
|
+
</form>
|
|
775
|
+
`);
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
// Logic Handlers attached to window
|
|
779
|
+
window.handleLogin = async (e) => {
|
|
780
|
+
e.preventDefault();
|
|
781
|
+
const email = e.target.email.value;
|
|
782
|
+
const pass = e.target.password.value;
|
|
783
|
+
const success = await authState.login(email, pass);
|
|
784
|
+
if(success) document.querySelector('.modal-overlay')?.remove();
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
window.handleSignup = async (e) => {
|
|
788
|
+
e.preventDefault();
|
|
789
|
+
const data = {
|
|
790
|
+
firstName: e.target.firstName.value,
|
|
791
|
+
lastName: e.target.lastName.value,
|
|
792
|
+
email: e.target.email.value,
|
|
793
|
+
password: e.target.password.value
|
|
794
|
+
};
|
|
795
|
+
const success = await authState.signup(data);
|
|
796
|
+
if(success) {
|
|
797
|
+
document.querySelector('.modal-overlay')?.remove();
|
|
798
|
+
setTimeout(() => openLoginModal(), 300);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
window.handleUpdateProfile = async (e) => {
|
|
803
|
+
e.preventDefault();
|
|
804
|
+
const data = {
|
|
805
|
+
firstName: e.target.firstName.value,
|
|
806
|
+
lastName: e.target.lastName.value,
|
|
807
|
+
password: e.target.password.value
|
|
808
|
+
};
|
|
809
|
+
const updatedUser = { ...authState.user, ...data };
|
|
810
|
+
if(!data.password) delete updatedUser.password;
|
|
811
|
+
if(JSON.stringify(updatedUser) === JSON.stringify(authState.user))
|
|
812
|
+
toast.show('No changes made to update.', 'info');
|
|
813
|
+
else {
|
|
814
|
+
const success = await authState.updateProfile(updatedUser);
|
|
815
|
+
if(success) document.querySelector('.modal-overlay')?.remove();
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
// Expose Auth globally for logout
|
|
820
|
+
import { authState as auth } from '../services/auth.js';
|
|
821
|
+
window.auth = auth;
|
|
822
|
+
EOF
|
|
823
|
+
|
|
824
|
+
cat > src/components/Layout.js <<'EOF'
|
|
825
|
+
import { Navbar } from './Navbar.js';
|
|
826
|
+
|
|
827
|
+
export const Layout = () =>
|
|
828
|
+
`
|
|
829
|
+
<div id="nav-mount">${Navbar()}</div>
|
|
830
|
+
<main>
|
|
831
|
+
<div data-outlet></div>
|
|
832
|
+
</main>
|
|
833
|
+
<footer style="background: white; border-top: 1px solid #eaeaea; padding: 4rem 0; margin-top: 4rem;">
|
|
834
|
+
<div class="container" style="text-align:center;">
|
|
835
|
+
<h3 style="margin-bottom:1rem; opacity:0.8;">Refai.Code</h3>
|
|
836
|
+
<div style="display:flex; justify-content:center; gap:1.5rem; margin-bottom:2rem;">
|
|
837
|
+
<a href="#" style="color:#666; text-decoration:none;">Instagram</a>
|
|
838
|
+
<a href="#" style="color:#666; text-decoration:none;">Twitter</a>
|
|
839
|
+
<a href="#" style="color:#666; text-decoration:none;">GitHub</a>
|
|
840
|
+
</div>
|
|
841
|
+
<p style="color:#999; font-size:0.9rem;">© 2026. All rights reserved.</p>
|
|
842
|
+
</div>
|
|
843
|
+
</footer>
|
|
844
|
+
`;
|
|
845
|
+
EOF
|
|
846
|
+
|
|
847
|
+
# -----------------------------
|
|
848
|
+
# 6. Pages (Auth & Logic)
|
|
849
|
+
# -----------------------------
|
|
850
|
+
cat > src/pages/LoginPage.js <<'EOF'
|
|
851
|
+
import { authState } from '../services/auth.js';
|
|
852
|
+
|
|
853
|
+
export const LoginPage = () => {
|
|
854
|
+
setTimeout(() => {
|
|
855
|
+
const form = document.getElementById('loginForm');
|
|
856
|
+
if(form) {
|
|
857
|
+
form.onsubmit = async (e) => {
|
|
858
|
+
e.preventDefault();
|
|
859
|
+
const email = form.email.value;
|
|
860
|
+
const pass = form.password.value;
|
|
861
|
+
const success = await authState.login(email, pass);
|
|
862
|
+
if(success) window.location.href = '/';
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
}, 0);
|
|
866
|
+
|
|
867
|
+
return `
|
|
868
|
+
<div class="auth-container">
|
|
869
|
+
<div class="glass-panel auth-box">
|
|
870
|
+
<div style="text-align:center; margin-bottom:2rem;">
|
|
871
|
+
<h2 style="font-size:2rem; margin-bottom:0.5rem;">Welcome Back</h2>
|
|
872
|
+
<p style="color:#6b7280;">Enter your credentials to access your account.</p>
|
|
873
|
+
</div>
|
|
874
|
+
|
|
875
|
+
<form id="loginForm">
|
|
876
|
+
<label style="font-weight:600; font-size:0.9rem; margin-bottom:0.5rem; display:block;">Email Address</label>
|
|
877
|
+
<input type="email" name="email" value="admin@refai.code" required placeholder="name@example.com">
|
|
878
|
+
|
|
879
|
+
<label style="font-weight:600; font-size:0.9rem; margin-bottom:0.5rem; display:block;">Password</label>
|
|
880
|
+
<input type="password" name="password" value="admin123" required placeholder="••••••••">
|
|
881
|
+
|
|
882
|
+
<button type="submit" class="btn btn-primary" style="width:100%; padding:0.9rem;">Sign In</button>
|
|
883
|
+
</form>
|
|
884
|
+
|
|
885
|
+
<div style="display:flex; align-items:center; margin: 2rem 0; color:#9ca3af; font-size:0.9rem;">
|
|
886
|
+
<div style="flex:1; height:1px; background:#e5e7eb;"></div>
|
|
887
|
+
<span style="padding:0 1rem;">OR CONTINUE WITH</span>
|
|
888
|
+
<div style="flex:1; height:1px; background:#e5e7eb;"></div>
|
|
889
|
+
</div>
|
|
890
|
+
|
|
891
|
+
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:1rem;">
|
|
892
|
+
<button class="btn btn-social">
|
|
893
|
+
<svg width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12.61C5,9.5 7.81,5.7 12.19,5.7C13.63,5.7 15.71,6.5 16.55,7.34L19.07,4.8C16.8,2.77 14.28,2.15 12.18,2.15C5.83,2.15 1.15,7.39 1.15,12.61C1.15,17.97 4.96,22.85 12.18,22.85C18.27,22.85 22.85,17.7 22.85,12.61C22.85,11.75 22.75,11.53 22.65,11.1H21.35Z"/></svg> Google
|
|
894
|
+
</button>
|
|
895
|
+
<button class="btn btn-social">
|
|
896
|
+
<svg width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z"/></svg> GitHub
|
|
897
|
+
</button>
|
|
898
|
+
</div>
|
|
899
|
+
|
|
900
|
+
<p style="text-align:center; margin-top:2rem; font-size:0.9rem;">
|
|
901
|
+
Don't have an account? <a href="/signup" data-link style="color:var(--primary); font-weight:600; text-decoration:none;">Sign Up</a>
|
|
902
|
+
</p>
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
`;
|
|
906
|
+
};
|
|
907
|
+
EOF
|
|
908
|
+
|
|
909
|
+
cat > src/pages/SignupPage.js <<'EOF'
|
|
910
|
+
import { authState } from '../services/auth.js';
|
|
911
|
+
|
|
912
|
+
export const SignupPage = () => {
|
|
913
|
+
setTimeout(() => {
|
|
914
|
+
const form = document.getElementById('signupForm');
|
|
915
|
+
if(form) {
|
|
916
|
+
form.onsubmit = async (e) => {
|
|
917
|
+
e.preventDefault();
|
|
918
|
+
const data = {
|
|
919
|
+
firstName: form.firstName.value,
|
|
920
|
+
lastName: form.lastName.value,
|
|
921
|
+
email: form.email.value,
|
|
922
|
+
password: form.password.value
|
|
923
|
+
};
|
|
924
|
+
const success = await authState.signup(data);
|
|
925
|
+
if(success) window.location.href = '/login';
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
}, 0);
|
|
929
|
+
|
|
930
|
+
return `
|
|
931
|
+
<div class="auth-container">
|
|
932
|
+
<div class="glass-panel auth-box">
|
|
933
|
+
<div style="text-align:center; margin-bottom:2rem;">
|
|
934
|
+
<h2 style="font-size:2rem; margin-bottom:0.5rem;">Join Us</h2>
|
|
935
|
+
<p style="color:#6b7280;">Create your account to get started.</p>
|
|
936
|
+
</div>
|
|
937
|
+
|
|
938
|
+
<form id="signupForm">
|
|
939
|
+
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:1rem;">
|
|
940
|
+
<div>
|
|
941
|
+
<label style="font-weight:600; font-size:0.9rem; margin-bottom:0.5rem; display:block;">First Name</label>
|
|
942
|
+
<input type="text" name="firstName" required placeholder="John">
|
|
943
|
+
</div>
|
|
944
|
+
<div>
|
|
945
|
+
<label style="font-weight:600; font-size:0.9rem; margin-bottom:0.5rem; display:block;">Last Name</label>
|
|
946
|
+
<input type="text" name="lastName" required placeholder="Doe">
|
|
947
|
+
</div>
|
|
948
|
+
</div>
|
|
949
|
+
|
|
950
|
+
<label style="font-weight:600; font-size:0.9rem; margin-bottom:0.5rem; display:block;">Email Address</label>
|
|
951
|
+
<input type="email" name="email" required placeholder="name@example.com">
|
|
952
|
+
|
|
953
|
+
<label style="font-weight:600; font-size:0.9rem; margin-bottom:0.5rem; display:block;">Password</label>
|
|
954
|
+
<input type="password" name="password" required placeholder="Create a password">
|
|
955
|
+
|
|
956
|
+
<button type="submit" class="btn btn-primary" style="width:100%; padding:0.9rem;">Create Account</button>
|
|
957
|
+
</form>
|
|
958
|
+
|
|
959
|
+
<p style="text-align:center; margin-top:2rem; font-size:0.9rem;">
|
|
960
|
+
Already have an account? <a href="/login" data-link style="color:var(--primary); font-weight:600; text-decoration:none;">Log In</a>
|
|
961
|
+
</p>
|
|
962
|
+
</div>
|
|
963
|
+
</div>
|
|
964
|
+
`;
|
|
965
|
+
};
|
|
966
|
+
EOF
|
|
967
|
+
|
|
968
|
+
# Standard Pages (Home, Products, Details)
|
|
969
|
+
cat > src/pages/HomePage.js <<'EOF'
|
|
970
|
+
import { api } from '../services/api.js';
|
|
971
|
+
|
|
972
|
+
export const HomePage = async () => {
|
|
973
|
+
return `
|
|
974
|
+
<header style="padding: 8rem 2rem; text-align:center; background: radial-gradient(circle at center, rgba(79,70,229,0.1), transparent 70%);">
|
|
975
|
+
<div class="container">
|
|
976
|
+
<span style="background:rgba(79,70,229,0.1); color:var(--primary); padding:0.4rem 1rem; border-radius:30px; font-weight:600; font-size:0.9rem;">
|
|
977
|
+
v2.0 Ultimate
|
|
978
|
+
</span>
|
|
979
|
+
<h1 style="font-size:4rem; margin:1.5rem 0; line-height:1.1;">
|
|
980
|
+
Build with <span style="background: linear-gradient(to right, #4f46e5, #ec4899); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Precision</span>.
|
|
981
|
+
</h1>
|
|
982
|
+
<p style="font-size:1.4rem; color:#6b7280; max-width:600px; margin:0 auto 2.5rem;">
|
|
983
|
+
A fully functional MVC architecture with Authentication, Routing, and Live Database integration.
|
|
984
|
+
</p>
|
|
985
|
+
<div style="display:flex; gap:1rem; justify-content:center;">
|
|
986
|
+
<a href="/products" class="btn btn-primary" style="padding:1rem 2rem; font-size:1.1rem;" data-link>Fetch Data</a>
|
|
987
|
+
<a href="https://github.com/refai" target="_blank" class="btn btn-social" style="width:auto; padding:1rem 2rem; font-size:1.1rem;">Documentation</a>
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
</header>
|
|
991
|
+
|
|
992
|
+
<section id="about" style="background:white; padding:6rem 2rem;">
|
|
993
|
+
<div class="container">
|
|
994
|
+
<div style="text-align:center; margin-bottom:4rem;">
|
|
995
|
+
<h2 style="font-size:2.5rem; margin-bottom:1rem;">Why Refai.Code?</h2>
|
|
996
|
+
<p style="color:#6b7280;">Built for modern developers who need speed and structure.</p>
|
|
997
|
+
</div>
|
|
998
|
+
|
|
999
|
+
<div class="grid">
|
|
1000
|
+
<div class="glass-panel" style="text-align:center; border:none; background:#f9fafb;">
|
|
1001
|
+
<div style="font-size:2.5rem; margin-bottom:1rem;">⚡</div>
|
|
1002
|
+
<h3 style="margin-bottom:0.5rem;">Concurrent Run</h3>
|
|
1003
|
+
<p style="color:#6b7280;">Vite & Server run in a single command.</p>
|
|
1004
|
+
</div>
|
|
1005
|
+
<div class="glass-panel" style="text-align:center; border:none; background:#f9fafb;">
|
|
1006
|
+
<div style="font-size:2.5rem; margin-bottom:1rem;">🔐</div>
|
|
1007
|
+
<h3 style="margin-bottom:0.5rem;">Real Auth</h3>
|
|
1008
|
+
<p style="color:#6b7280;">Users saved to JSON DB with persistent login.</p>
|
|
1009
|
+
</div>
|
|
1010
|
+
<div class="glass-panel" style="text-align:center; border:none; background:#f9fafb;">
|
|
1011
|
+
<div style="font-size:2.5rem; margin-bottom:1rem;">💅</div>
|
|
1012
|
+
<h3 style="margin-bottom:0.5rem;">Glass UI</h3>
|
|
1013
|
+
<p style="color:#6b7280;">Modern aesthetics with blurred backgrounds.</p>
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
</section>
|
|
1018
|
+
`;
|
|
1019
|
+
};
|
|
1020
|
+
EOF
|
|
1021
|
+
|
|
1022
|
+
cat > src/pages/ProductsPage.js <<'EOF'
|
|
1023
|
+
import { api } from '../services/api.js';
|
|
1024
|
+
import { renderStars } from '../utils/helpers.js';
|
|
1025
|
+
|
|
1026
|
+
export const ProductsPage = async () => {
|
|
1027
|
+
let products = [];
|
|
1028
|
+
try {
|
|
1029
|
+
products = await api.get('/products');
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
// Fallback
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
return `<div class="container" style="padding-top:4rem;">
|
|
1035
|
+
<h1 style="margin-bottom:2rem;">Featured Products</h1>
|
|
1036
|
+
<div class="grid">
|
|
1037
|
+
${products.map(p => `
|
|
1038
|
+
<div class="card">
|
|
1039
|
+
<img src="${p.image}" class="card-img" alt="${p.name}">
|
|
1040
|
+
<div class="card-body">
|
|
1041
|
+
<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:0.5rem;">
|
|
1042
|
+
<h3 style="font-size:1.1rem; margin:0; font-weight:700;">${p.name}</h3>
|
|
1043
|
+
<span style="font-size:0.9rem; font-weight:600; color:var(--primary);">$${p.price}</span>
|
|
1044
|
+
</div>
|
|
1045
|
+
<div style="margin-bottom:1rem; font-size:0.9rem; color:#f59e0b;">
|
|
1046
|
+
${renderStars(p.rating)}
|
|
1047
|
+
</div>
|
|
1048
|
+
<p style="font-size:0.9rem; color:#6b7280; flex:1; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; margin-bottom:1.5rem;">
|
|
1049
|
+
${p.description}
|
|
1050
|
+
</p>
|
|
1051
|
+
<a href="/products/${p.id}" class="btn btn-primary" style="width:100%; justify-content:center;" data-link>View Details</a>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
`).join('')}
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>`;
|
|
1057
|
+
};
|
|
1058
|
+
EOF
|
|
1059
|
+
|
|
1060
|
+
cat > src/pages/ProductDetailsPage.js <<'EOF'
|
|
1061
|
+
import { api } from '../services/api.js';
|
|
1062
|
+
import { renderStars } from '../utils/helpers.js';
|
|
1063
|
+
|
|
1064
|
+
export const ProductDetailsPage = async ({ params }) => {
|
|
1065
|
+
try {
|
|
1066
|
+
const p = await api.get(`/products/${params.id}`);
|
|
1067
|
+
return `<div class="container" style="padding-top:4rem;">
|
|
1068
|
+
<a href="/products" data-link style="display:inline-flex; align-items:center; color:#6b7280; text-decoration:none; margin-bottom:2rem; font-weight:500;">
|
|
1069
|
+
<svg style="width:20px; margin-right:5px;" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
|
1070
|
+
Back to Products
|
|
1071
|
+
</a>
|
|
1072
|
+
|
|
1073
|
+
<div class="glass-panel" style="display:grid; grid-template-columns: 1fr 1fr; gap:3rem; padding:3rem;">
|
|
1074
|
+
<img src="${p.image}" class="product-detail-img" style="width:100%; border-radius:12px;" alt="${p.name}">
|
|
1075
|
+
<div>
|
|
1076
|
+
<h1 style="font-size:2.5rem; margin:0 0 1rem 0;">${p.name}</h1>
|
|
1077
|
+
<div style="display:flex; align-items:center; gap:1rem; margin-bottom:2rem;">
|
|
1078
|
+
<span style="font-size:1.5rem; font-weight:700; color:var(--primary);">$${p.price}</span>
|
|
1079
|
+
<div style="border-left:1px solid #ddd; padding-left:1rem; font-size:1.1rem; color:#f59e0b;">
|
|
1080
|
+
${renderStars(p.rating)}
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
<p style="color:#4b5563; line-height:1.7; font-size:1.1rem; margin-bottom:3rem;">
|
|
1084
|
+
${p.description}
|
|
1085
|
+
</p>
|
|
1086
|
+
<button class="btn btn-primary" style="padding:1rem 2rem; font-size:1.1rem; width:100%;" onclick="alert('Added to cart!')">
|
|
1087
|
+
Add to Cart
|
|
1088
|
+
</button>
|
|
1089
|
+
</div>
|
|
1090
|
+
</div>`;
|
|
1091
|
+
} catch (e) {
|
|
1092
|
+
return `<div class="container"><h1>Product Not Found</h1></div>`;
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
EOF
|
|
1096
|
+
|
|
1097
|
+
# -----------------------------
|
|
1098
|
+
# 7. Main Entry Point
|
|
1099
|
+
# -----------------------------
|
|
1100
|
+
# Smart handling: Append to existing main.js instead of replacing
|
|
1101
|
+
if [ -f "src/main.js" ]; then
|
|
1102
|
+
echo "\n⚠️ src/main.js already exists."
|
|
1103
|
+
if read -q "choice?Do you want to append router code to it? (y/N) "; then
|
|
1104
|
+
echo "\n✅ Appending router logic to existing main.js..."
|
|
1105
|
+
|
|
1106
|
+
# Backup first
|
|
1107
|
+
cp src/main.js src/main.js.backup
|
|
1108
|
+
echo "📦 Backup created: src/main.js.backup"
|
|
1109
|
+
|
|
1110
|
+
# Append router imports and setup
|
|
1111
|
+
cat >> src/main.js <<'EOF'
|
|
1112
|
+
|
|
1113
|
+
// ===== Refai.Code Router Setup (Auto-generated) =====
|
|
1114
|
+
import { LiteRouter } from './router/LiteRouter.js';
|
|
1115
|
+
import { Layout } from './components/Layout.js';
|
|
1116
|
+
import { Navbar } from './components/Navbar.js';
|
|
1117
|
+
import { authState } from './services/auth.js';
|
|
1118
|
+
|
|
1119
|
+
import { HomePage } from './pages/HomePage.js';
|
|
1120
|
+
import { ProductsPage } from './pages/ProductsPage.js';
|
|
1121
|
+
import { ProductDetailsPage } from './pages/ProductDetailsPage.js';
|
|
1122
|
+
import { LoginPage } from './pages/LoginPage.js';
|
|
1123
|
+
import { SignupPage } from './pages/SignupPage.js';
|
|
1124
|
+
|
|
1125
|
+
const routes = [
|
|
1126
|
+
{ path: '/', component: HomePage, layout: Layout, title: 'Home | Refai.Code' },
|
|
1127
|
+
{ path: '/products', component: ProductsPage, layout: Layout, title: 'Products' },
|
|
1128
|
+
{ path: '/products/:id', component: ProductDetailsPage, layout: Layout, title: 'Details' },
|
|
1129
|
+
{ path: '/login', component: LoginPage, layout: Layout, title: 'Login' },
|
|
1130
|
+
{ path: '/logout', component: () => { authState.logout(); return '<div></div>'; } },
|
|
1131
|
+
{ path: '/signup', component: SignupPage, layout: Layout, title: 'Signup' },
|
|
1132
|
+
{ path: '*', component: () => '<h1 class="container">404</h1>', layout: Layout }
|
|
1133
|
+
];
|
|
1134
|
+
|
|
1135
|
+
authState.subscribe(() => {
|
|
1136
|
+
const el = document.getElementById('nav-mount');
|
|
1137
|
+
if (el) el.innerHTML = Navbar();
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
const app = new LiteRouter(routes, '#app');
|
|
1141
|
+
app.init();
|
|
1142
|
+
EOF
|
|
1143
|
+
else
|
|
1144
|
+
echo "\n⏭️ Skipped modifying main.js"
|
|
1145
|
+
fi
|
|
1146
|
+
else
|
|
1147
|
+
# File doesn't exist, create new one
|
|
1148
|
+
cat > src/main.js <<'EOF'
|
|
1149
|
+
import './styles/main.css';
|
|
1150
|
+
import { LiteRouter } from './router/LiteRouter.js';
|
|
1151
|
+
import { Layout } from './components/Layout.js';
|
|
1152
|
+
import { Navbar } from './components/Navbar.js';
|
|
1153
|
+
import { authState } from './services/auth.js';
|
|
1154
|
+
|
|
1155
|
+
import { HomePage } from './pages/HomePage.js';
|
|
1156
|
+
import { ProductsPage } from './pages/ProductsPage.js';
|
|
1157
|
+
import { ProductDetailsPage } from './pages/ProductDetailsPage.js';
|
|
1158
|
+
import { LoginPage } from './pages/LoginPage.js';
|
|
1159
|
+
import { SignupPage } from './pages/SignupPage.js';
|
|
1160
|
+
|
|
1161
|
+
const routes = [
|
|
1162
|
+
{ path: '/', component: HomePage, layout: Layout, title: 'Home | Refai.Code' },
|
|
1163
|
+
{ path: '/products', component: ProductsPage, layout: Layout, title: 'Products' },
|
|
1164
|
+
{ path: '/products/:id', component: ProductDetailsPage, layout: Layout, title: 'Details' },
|
|
1165
|
+
{ path: '/login', component: LoginPage, layout: Layout, title: 'Login' },
|
|
1166
|
+
{ path: '/logout', component: () => { authState.logout(); return '<div></div>'; } },
|
|
1167
|
+
{ path: '/signup', component: SignupPage, layout: Layout, title: 'Signup' },
|
|
1168
|
+
{ path: '*', component: () => '<h1 class="container">404</h1>', layout: Layout }
|
|
1169
|
+
];
|
|
1170
|
+
|
|
1171
|
+
// Subscribe Navbar to Auth Changes
|
|
1172
|
+
authState.subscribe(() => {
|
|
1173
|
+
const el = document.getElementById('nav-mount');
|
|
1174
|
+
if (el) el.innerHTML = Navbar();
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
const app = new LiteRouter(routes, '#app');
|
|
1178
|
+
app.init();
|
|
1179
|
+
EOF
|
|
1180
|
+
fi
|
|
1181
|
+
|
|
1182
|
+
# -----------------------------
|
|
1183
|
+
# 8. Dependency & Concurrent
|
|
1184
|
+
# -----------------------------
|
|
1185
|
+
# Create DB.json
|
|
1186
|
+
cat > db.json <<'EOF'
|
|
1187
|
+
{
|
|
1188
|
+
"users": [
|
|
1189
|
+
{
|
|
1190
|
+
"id": 1,
|
|
1191
|
+
"email": "admin@refai.code",
|
|
1192
|
+
"password": "admin123",
|
|
1193
|
+
"firstName": "Super",
|
|
1194
|
+
"lastName": "Admin",
|
|
1195
|
+
"avatar": "https://ui-avatars.com/api/?name=Refai+Code&background=4f46e5&color=fff"
|
|
1196
|
+
}
|
|
1197
|
+
],
|
|
1198
|
+
"products": [
|
|
1199
|
+
{
|
|
1200
|
+
"id": 1,
|
|
1201
|
+
"name": "Sony WH-1000XM5",
|
|
1202
|
+
"price": 348,
|
|
1203
|
+
"rating": 4.8,
|
|
1204
|
+
"image": "https://images.unsplash.com/photo-1618366712010-f4ae9c647dcb?auto=format&fit=crop&w=600&q=80",
|
|
1205
|
+
"description": "Industry-leading noise canceling with two processors controlling 8 microphones for unprecedented noise cancellation."
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
"id": 2,
|
|
1209
|
+
"name": "Apple MacBook Air M2",
|
|
1210
|
+
"price": 1099,
|
|
1211
|
+
"rating": 4.9,
|
|
1212
|
+
"image": "https://images.unsplash.com/photo-1517336714731-489689fd1ca4?auto=format&fit=crop&w=600&q=80",
|
|
1213
|
+
"description": "Redesigned around the next-generation M2 chip. Strikingly thin and brings exceptional speed and power efficiency."
|
|
1214
|
+
}
|
|
1215
|
+
]
|
|
1216
|
+
}
|
|
1217
|
+
EOF
|
|
1218
|
+
|
|
1219
|
+
# -----------------------------
|
|
1220
|
+
# 8. Package Installation
|
|
1221
|
+
# -----------------------------
|
|
1222
|
+
echo "\n📦 Installing Dependencies..."
|
|
1223
|
+
if [ -f "pnpm-lock.yaml" ]; then PM="pnpm"; elif [ -f "yarn.lock" ]; then PM="yarn"; else PM="npm"; fi
|
|
1224
|
+
|
|
1225
|
+
$PM install
|
|
1226
|
+
installed_packages+=("Core dependencies")
|
|
1227
|
+
|
|
1228
|
+
if [ "$tail" = true ]; then
|
|
1229
|
+
echo "🎨 Setting up TailwindCSS..."
|
|
1230
|
+
$PM install -D tailwindcss postcss autoprefixer
|
|
1231
|
+
|
|
1232
|
+
# Initialize tailwind config
|
|
1233
|
+
if [ "$PM" = "pnpm" ]; then
|
|
1234
|
+
pnpm exec tailwindcss init -p
|
|
1235
|
+
elif [ "$PM" = "yarn" ]; then
|
|
1236
|
+
yarn tailwindcss init -p
|
|
1237
|
+
else
|
|
1238
|
+
npx -y tailwindcss init -p
|
|
1239
|
+
fi
|
|
1240
|
+
|
|
1241
|
+
cat > src/styles/tailwind.css <<EOF
|
|
1242
|
+
@tailwind base;
|
|
1243
|
+
@tailwind components;
|
|
1244
|
+
@tailwind utilities;
|
|
1245
|
+
EOF
|
|
1246
|
+
echo "@import './tailwind.css';" | cat - src/styles/main.css > temp && mv temp src/styles/main.css
|
|
1247
|
+
installed_packages+=("TailwindCSS")
|
|
1248
|
+
fi
|
|
1249
|
+
|
|
1250
|
+
if [ "$axio" = true ]; then
|
|
1251
|
+
echo "📡 Installing Axios..."
|
|
1252
|
+
$PM install axios
|
|
1253
|
+
installed_packages+=("Axios")
|
|
1254
|
+
fi
|
|
1255
|
+
|
|
1256
|
+
if [ "$json" = true ]; then
|
|
1257
|
+
echo "🚀 Setting up JSON Server..."
|
|
1258
|
+
$PM install -D json-server concurrently
|
|
1259
|
+
|
|
1260
|
+
# Inject Scripts into package.json (dev for Vite only, dev:full for both)
|
|
1261
|
+
node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.scripts=p.scripts||{};p.scripts['dev:full']='concurrently \"vite\" \"json-server db.json --port 3000\"';if(!p.scripts.dev || p.scripts.dev.includes('concurrently')) p.scripts.dev='vite';fs.writeFileSync('package.json',JSON.stringify(p,null,2));"
|
|
1262
|
+
installed_packages+=("JSON Server" "Concurrently")
|
|
1263
|
+
fi
|
|
1264
|
+
|
|
1265
|
+
# PWA Setup
|
|
1266
|
+
if [ "$pwa" = true ]; then
|
|
1267
|
+
echo "📱 Setting up PWA..."
|
|
1268
|
+
$PM install -D vite-plugin-pwa
|
|
1269
|
+
installed_packages+=("Vite PWA Plugin")
|
|
1270
|
+
cat > vite.config.js <<EOF
|
|
1271
|
+
import { defineConfig } from 'vite';
|
|
1272
|
+
import path from 'path';
|
|
1273
|
+
import { VitePWA } from 'vite-plugin-pwa';
|
|
1274
|
+
|
|
1275
|
+
export default defineConfig({
|
|
1276
|
+
resolve: { alias: { '@': path.resolve(__dirname, './src') } },
|
|
1277
|
+
plugins: [
|
|
1278
|
+
VitePWA({ registerType: 'autoUpdate' }),
|
|
1279
|
+
]
|
|
1280
|
+
});
|
|
1281
|
+
EOF
|
|
1282
|
+
else
|
|
1283
|
+
cat > vite.config.js <<EOF
|
|
1284
|
+
import { defineConfig } from 'vite';
|
|
1285
|
+
import path from 'path';
|
|
1286
|
+
|
|
1287
|
+
export default defineConfig({
|
|
1288
|
+
resolve: { alias: { '@': path.resolve(__dirname, './src') } },
|
|
1289
|
+
});
|
|
1290
|
+
EOF
|
|
1291
|
+
fi
|
|
1292
|
+
|
|
1293
|
+
# -----------------------------
|
|
1294
|
+
# Final Summary
|
|
1295
|
+
# -----------------------------
|
|
1296
|
+
echo "\n✅ \033[1;32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m"
|
|
1297
|
+
echo "\n🎉 \033[1;32mSetup Complete!\033[0m Created by Refai.Code\n"
|
|
1298
|
+
|
|
1299
|
+
if [ ${#installed_packages[@]} -gt 0 ]; then
|
|
1300
|
+
echo "📦 \033[1;36mInstalled Packages:\033[0m"
|
|
1301
|
+
for pkg in "${installed_packages[@]}"; do
|
|
1302
|
+
echo " ✓ $pkg"
|
|
1303
|
+
done
|
|
1304
|
+
echo ""
|
|
1305
|
+
fi
|
|
1306
|
+
|
|
1307
|
+
echo "🚀 \033[1;33mNext Steps:\033[0m"
|
|
1308
|
+
echo " 1. Run \033[1m$PM run dev\033[0m to start Vite dev server"
|
|
1309
|
+
if [ "$json" = true ]; then
|
|
1310
|
+
echo " 2. Run \033[1m$PM run dev:full\033[0m to start Vite + JSON Server together"
|
|
1311
|
+
echo " 3. Or run \033[1mnpx json-server db.json --port 3000\033[0m in a separate terminal"
|
|
1312
|
+
fi
|
|
1313
|
+
echo "\n✅ \033[1;32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n"
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@refai.code/vite-boost",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite Vanilla JS booster script (optional Tailwind/Axios/PWA/JSON-server)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"bin": {
|
|
8
|
+
"vite-boost": "bin/vite-boost"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
}
|
|
16
|
+
}
|