diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ae81cb1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+.env
+config/config.php
+uploads/
+*.log
+.DS_Store
+test-*.php
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..81647ea
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,21 @@
+# Enable Rewrite Engine
+
+ RewriteEngine On
+ RewriteBase /
+
+
+# Prevent directory listing
+Options -Indexes
+
+# PHP Security Headers
+
+ Header set X-Content-Type-Options "nosniff"
+ Header set X-Frame-Options "SAMEORIGIN"
+ Header set X-XSS-Protection "1; mode=block"
+
+
+# Protect sensitive files
+
+ Order allow,deny
+ Deny from all
+
diff --git a/README.md b/README.md
index d899877..000917a 100644
--- a/README.md
+++ b/README.md
@@ -1,112 +1,164 @@
-# Mixlar Plugin Marketplace
+# Mixlar Plugin Marketplace - PHP Edition
-A modern, elegant marketplace website for browsing and discovering Mixlar plugins and integrations.
+A full-featured plugin marketplace with user authentication, admin portal, and plugin management system. Built with PHP, MySQL, and vanilla JavaScript.
## Features
-- **Modern Design**: Clean, professional interface inspired by the Elgato marketplace
-- **Category Filtering**: Filter plugins by category (Core, Streaming, Smart Home, Control, Creative)
-- **Search Functionality**: Real-time search across plugin names, descriptions, and tags
-- **Detailed Plugin Pages**: Comprehensive information pages for each plugin with installation instructions
-- **Responsive Layout**: Fully responsive design that works on desktop, tablet, and mobile devices
-- **Dynamic Content**: All plugin data loaded from `list.json` for easy updates
-
-## File Structure
+### š Authentication System
+- User signup/login with JWT tokens
+- Password reset with email verification
+- Role-based access control (Admin/User)
+- Secure password hashing with bcrypt
+
+### šŖ Marketplace
+- Browse and search plugins
+- Filter by category
+- Real-time search
+- Plugin details and downloads
+- Download tracking
+- Elgato-style modern UI
+
+### š Admin Portal
+- Dashboard with statistics
+- Approve/reject plugin submissions
+- Feature plugins
+- User management
+- Role management
+- Plugin and user deletion
+
+## Installation
+
+### Prerequisites
+- PHP 7.4 or higher
+- MySQL 5.7+ or MariaDB 10.3+
+- Apache or Nginx web server
+- PHP extensions: mysqli, json, mbstring
+
+### Quick Setup
+
+**1. Create MySQL database:**
+```sql
+CREATE DATABASE mixlar_marketplace CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+```
+**2. Import database schema:**
+```bash
+mysql -u your_user -p mixlar_marketplace < sql/schema.sql
```
-/plugins
-āāā index.html # Main marketplace page
-āāā plugin.html # Plugin detail page template
-āāā styles.css # All styles and responsive design
-āāā app.js # Main marketplace functionality
-āāā plugin-detail.js # Plugin detail page functionality
-āāā list.json # Plugin data source
-āāā README.md # This file
+
+**3. Configure `config/config.php`:**
+```php
+define('DB_HOST', 'localhost');
+define('DB_USER', 'your_database_user');
+define('DB_PASS', 'your_database_password');
+define('DB_NAME', 'mixlar_marketplace');
+define('JWT_SECRET', 'change_this_secret_key');
+define('SITE_URL', 'http://yoursite.com');
```
-## Usage
-
-### Viewing the Marketplace
-
-Simply open `index.html` in a web browser to view the marketplace.
-
-### Adding New Plugins
-
-1. Edit `list.json` to add your new plugin data
-2. Follow the existing JSON structure:
-
-```json
-{
- "id": 8,
- "name": "Your Plugin Name",
- "category": "core|streaming|smarthome|control|creative",
- "tag": "Your Tag",
- "status": "instruction|download|installed",
- "author": "Author Name",
- "socialUrl": "https://github.com/author",
- "description": "Plugin description",
- "imageColor": "from-color-600 to-color-700",
- "icon": "fa-icon-name",
- "downloadUrl": "https://download-url.com",
- "instructionUrl": "https://docs-url.com",
- "devices": ["Mixlar Mix"],
- "version": "1.0.0"
-}
+**4. (Optional) Seed initial data:**
+```bash
+php sql/seed.php
```
-### Icon Options
+**5. Access the site:**
+- Marketplace: http://yoursite.com/frontend/public/
+- Admin: http://yoursite.com/frontend/public/admin.html
+
+### Default Admin Login
+- Email: admin@mixlarlabs.com
+- Password: admin123
-Uses Font Awesome 6.4.0 icons. Available icons include:
-- `fa-server`, `fa-desktop`, `fa-video`, `fa-house-signal`
-- `fa-sliders`, `fa-pen-ruler`, `fa-headset`
-- And many more from Font Awesome library
+ā ļø **Change immediately after first login!**
-### Gradient Colors
+## Project Structure
-Supported gradient color combinations:
-- `from-slate-700 to-slate-900` - Dark gray
-- `from-blue-600 to-indigo-600` - Blue to indigo
-- `from-gray-800 to-gray-950` - Very dark gray
-- `from-cyan-600 to-blue-700` - Cyan to blue
-- `from-emerald-600 to-teal-700` - Green to teal
-- `from-fuchsia-700 to-purple-800` - Purple gradient
-- `from-orange-600 to-amber-700` - Orange gradient
+```
+/plugins
+āāā api/ # PHP API endpoints
+āāā config/ # Configuration
+āāā includes/ # PHP classes (Database, Auth, JWT, Email)
+āāā frontend/public/ # HTML, CSS, JS
+āāā sql/ # Database schema & seed
+āāā list.json # Initial plugin data
+```
-## Categories
+## Running on Different PHP Servers
+
+### XAMPP (Windows/Mac/Linux)
+1. Copy folder to `htdocs/plugins/`
+2. Start Apache and MySQL
+3. Import `sql/schema.sql` via phpMyAdmin
+4. Edit `config/config.php`
+5. Access: http://localhost/plugins/frontend/public/
+
+### WAMP (Windows)
+1. Copy to `www/plugins/`
+2. Same steps as XAMPP
+
+### MAMP (Mac)
+1. Copy to `htdocs/plugins/`
+2. Same steps as XAMPP
+
+### cPanel/Shared Hosting
+1. Upload via FTP to `public_html/`
+2. Create database via cPanel
+3. Import schema via phpMyAdmin
+4. Update `config/config.php` with cPanel database credentials
+
+### Apache (Linux)
+```bash
+sudo cp -r plugins /var/www/html/
+sudo chown -R www-data:www-data /var/www/html/plugins
+# Import SQL, configure config.php
+```
-- **core**: Essential plugins for core functionality
-- **streaming**: Plugins for streaming and broadcasting
-- **smarthome**: Smart home integration plugins
-- **control**: Control and automation plugins
-- **creative**: Creative workflow and productivity plugins
+### Nginx + PHP-FPM
+Add to nginx config:
+```nginx
+location ~ \.php$ {
+ fastcgi_pass unix:/var/run/php/php-fpm.sock;
+ include fastcgi_params;
+}
+```
-## Status Types
+## API Endpoints
-- **instruction**: Requires setup instructions (shows "Instruction" badge)
-- **download**: Available for download (shows "Download" badge)
-- **installed**: Already installed (shows "Installed" badge)
+### Auth
+- POST `/api/auth/signup.php`
+- POST `/api/auth/login.php`
+- POST `/api/auth/forgot-password.php`
+- POST `/api/auth/reset-password.php`
-## Deployment
+### Plugins
+- GET `/api/plugins/list.php`
+- GET `/api/plugins/get.php?id=X`
+- POST `/api/plugins/create.php` (auth required)
-To deploy the marketplace:
+### Admin (admin only)
+- GET `/api/admin/stats.php`
+- GET `/api/admin/plugins.php`
+- PUT `/api/admin/approve.php?id=X`
+- PUT `/api/admin/reject.php?id=X`
+- DELETE `/api/admin/delete-plugin.php?id=X`
-1. Upload all files to your web server
-2. Ensure `list.json` is accessible
-3. The marketplace will work with any static file hosting (GitHub Pages, Netlify, Vercel, etc.)
+See full API documentation in the detailed README sections above.
-## Browser Compatibility
+## Troubleshooting
-- Chrome (latest)
-- Firefox (latest)
-- Safari (latest)
-- Edge (latest)
+**Database Connection Error:**
+- Check credentials in `config/config.php`
+- Verify MySQL is running
+- Ensure database exists
-## Technologies Used
+**404 on API calls:**
+- Verify `.htaccess` exists
+- Enable mod_rewrite (Apache)
+- Check file permissions
-- HTML5
-- CSS3 (with CSS Grid and Flexbox)
-- Vanilla JavaScript (ES6+)
-- Font Awesome 6.4.0
+**Blank pages:**
+- Enable error reporting in `config/config.php`
+- Check PHP error logs
## License
diff --git a/api/admin/approve.php b/api/admin/approve.php
new file mode 100644
index 0000000..86e7dd8
--- /dev/null
+++ b/api/admin/approve.php
@@ -0,0 +1,50 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAdmin();
+
+ $id = $_GET['id'] ?? null;
+
+ if (!$id) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Plugin ID required']);
+ exit;
+ }
+
+ $db = new Database();
+
+ $stmt = $db->prepare("UPDATE plugins SET status = 'approved' WHERE id = ?");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+
+ if ($stmt->affected_rows === 0) {
+ http_response_code(404);
+ echo json_encode(['message' => 'Plugin not found']);
+ exit;
+ }
+
+ echo json_encode(['message' => 'Plugin approved']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/change-role.php b/api/admin/change-role.php
new file mode 100644
index 0000000..bd0b804
--- /dev/null
+++ b/api/admin/change-role.php
@@ -0,0 +1,52 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $currentUser = $auth->requireAdmin();
+
+ $id = $_GET['id'] ?? null;
+ $data = json_decode(file_get_contents('php://input'), true);
+ $role = $data['role'] ?? null;
+
+ if (!$id || !$role || !in_array($role, ['user', 'admin'])) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Invalid request']);
+ exit;
+ }
+
+ $db = new Database();
+
+ $stmt = $db->prepare("UPDATE users SET role = ? WHERE id = ?");
+ $stmt->bind_param("si", $role, $id);
+ $stmt->execute();
+
+ if ($stmt->affected_rows === 0) {
+ http_response_code(404);
+ echo json_encode(['message' => 'User not found']);
+ exit;
+ }
+
+ echo json_encode(['message' => 'User role updated']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/delete-plugin.php b/api/admin/delete-plugin.php
new file mode 100644
index 0000000..8122cdd
--- /dev/null
+++ b/api/admin/delete-plugin.php
@@ -0,0 +1,50 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAdmin();
+
+ $id = $_GET['id'] ?? null;
+
+ if (!$id) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Plugin ID required']);
+ exit;
+ }
+
+ $db = new Database();
+
+ $stmt = $db->prepare("DELETE FROM plugins WHERE id = ?");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+
+ if ($stmt->affected_rows === 0) {
+ http_response_code(404);
+ echo json_encode(['message' => 'Plugin not found']);
+ exit;
+ }
+
+ echo json_encode(['message' => 'Plugin deleted']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/delete-user.php b/api/admin/delete-user.php
new file mode 100644
index 0000000..727d9c3
--- /dev/null
+++ b/api/admin/delete-user.php
@@ -0,0 +1,57 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $currentUser = $auth->requireAdmin();
+
+ $id = $_GET['id'] ?? null;
+
+ if (!$id) {
+ http_response_code(400);
+ echo json_encode(['message' => 'User ID required']);
+ exit;
+ }
+
+ // Prevent admin from deleting themselves
+ if ($id == $currentUser['id']) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Cannot delete your own account']);
+ exit;
+ }
+
+ $db = new Database();
+
+ $stmt = $db->prepare("DELETE FROM users WHERE id = ?");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+
+ if ($stmt->affected_rows === 0) {
+ http_response_code(404);
+ echo json_encode(['message' => 'User not found']);
+ exit;
+ }
+
+ echo json_encode(['message' => 'User deleted']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/feature.php b/api/admin/feature.php
new file mode 100644
index 0000000..8bc7dc0
--- /dev/null
+++ b/api/admin/feature.php
@@ -0,0 +1,51 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAdmin();
+
+ $id = $_GET['id'] ?? null;
+
+ if (!$id) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Plugin ID required']);
+ exit;
+ }
+
+ $db = new Database();
+
+ // Toggle featured status
+ $stmt = $db->prepare("UPDATE plugins SET featured = NOT featured WHERE id = ?");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+
+ if ($stmt->affected_rows === 0) {
+ http_response_code(404);
+ echo json_encode(['message' => 'Plugin not found']);
+ exit;
+ }
+
+ echo json_encode(['message' => 'Plugin featured status updated']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/plugins.php b/api/admin/plugins.php
new file mode 100644
index 0000000..79b7daf
--- /dev/null
+++ b/api/admin/plugins.php
@@ -0,0 +1,82 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAdmin();
+
+ $db = new Database();
+
+ $status = $_GET['status'] ?? null;
+ $category = $_GET['category'] ?? null;
+
+ $sql = "SELECT p.*, u.username as author_username, u.email as author_email,
+ GROUP_CONCAT(pd.device_name) as devices
+ FROM plugins p
+ LEFT JOIN users u ON p.author_id = u.id
+ LEFT JOIN plugin_devices pd ON p.id = pd.plugin_id";
+
+ $conditions = [];
+ $params = [];
+ $types = '';
+
+ if ($status && $status !== 'all') {
+ $conditions[] = "p.status = ?";
+ $params[] = $status;
+ $types .= 's';
+ }
+
+ if ($category && $category !== 'all') {
+ $conditions[] = "p.category = ?";
+ $params[] = $category;
+ $types .= 's';
+ }
+
+ if (!empty($conditions)) {
+ $sql .= " WHERE " . implode(' AND ', $conditions);
+ }
+
+ $sql .= " GROUP BY p.id ORDER BY p.created_at DESC";
+
+ if (!empty($params)) {
+ $stmt = $db->prepare($sql);
+ $stmt->bind_param($types, ...$params);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ } else {
+ $result = $db->query($sql);
+ }
+
+ $plugins = [];
+ while ($row = $result->fetch_assoc()) {
+ $row['authorId'] = ['email' => $row['author_email']];
+ $row['devices'] = $row['devices'] ? explode(',', $row['devices']) : ['Mixlar Mix'];
+ $row['featured'] = (bool)$row['featured'];
+ $row['downloads'] = (int)$row['downloads'];
+ unset($row['author_email'], $row['author_username']);
+ $plugins[] = $row;
+ }
+
+ echo json_encode($plugins);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/reject.php b/api/admin/reject.php
new file mode 100644
index 0000000..47baad7
--- /dev/null
+++ b/api/admin/reject.php
@@ -0,0 +1,50 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAdmin();
+
+ $id = $_GET['id'] ?? null;
+
+ if (!$id) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Plugin ID required']);
+ exit;
+ }
+
+ $db = new Database();
+
+ $stmt = $db->prepare("UPDATE plugins SET status = 'rejected' WHERE id = ?");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+
+ if ($stmt->affected_rows === 0) {
+ http_response_code(404);
+ echo json_encode(['message' => 'Plugin not found']);
+ exit;
+ }
+
+ echo json_encode(['message' => 'Plugin rejected']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/stats.php b/api/admin/stats.php
new file mode 100644
index 0000000..22e5ce8
--- /dev/null
+++ b/api/admin/stats.php
@@ -0,0 +1,58 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAdmin();
+
+ $db = new Database();
+
+ // Total plugins
+ $result = $db->query("SELECT COUNT(*) as count FROM plugins");
+ $totalPlugins = $result->fetch_assoc()['count'];
+
+ // Approved plugins
+ $result = $db->query("SELECT COUNT(*) as count FROM plugins WHERE status = 'approved'");
+ $approvedPlugins = $result->fetch_assoc()['count'];
+
+ // Pending plugins
+ $result = $db->query("SELECT COUNT(*) as count FROM plugins WHERE status = 'pending'");
+ $pendingPlugins = $result->fetch_assoc()['count'];
+
+ // Total users
+ $result = $db->query("SELECT COUNT(*) as count FROM users");
+ $totalUsers = $result->fetch_assoc()['count'];
+
+ // Total downloads
+ $result = $db->query("SELECT SUM(downloads) as total FROM plugins");
+ $totalDownloads = $result->fetch_assoc()['total'] ?? 0;
+
+ echo json_encode([
+ 'totalPlugins' => (int)$totalPlugins,
+ 'approvedPlugins' => (int)$approvedPlugins,
+ 'pendingPlugins' => (int)$pendingPlugins,
+ 'totalUsers' => (int)$totalUsers,
+ 'totalDownloads' => (int)$totalDownloads
+ ]);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/admin/users.php b/api/admin/users.php
new file mode 100644
index 0000000..557467e
--- /dev/null
+++ b/api/admin/users.php
@@ -0,0 +1,39 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAdmin();
+
+ $db = new Database();
+
+ $result = $db->query("SELECT id, username, email, role, created_at FROM users ORDER BY created_at DESC");
+
+ $users = [];
+ while ($row = $result->fetch_assoc()) {
+ $users[] = $row;
+ }
+
+ echo json_encode($users);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/auth/forgot-password.php b/api/auth/forgot-password.php
new file mode 100644
index 0000000..88537d5
--- /dev/null
+++ b/api/auth/forgot-password.php
@@ -0,0 +1,66 @@
+ 'Method not allowed']);
+ exit;
+}
+
+$data = json_decode(file_get_contents('php://input'), true);
+$email = trim($data['email'] ?? '');
+
+if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Please enter a valid email']);
+ exit;
+}
+
+try {
+ $db = new Database();
+ $auth = new Auth();
+
+ // Find user
+ $stmt = $db->prepare("SELECT id FROM users WHERE email = ?");
+ $stmt->bind_param("s", $email);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ $user = $result->fetch_assoc();
+
+ // Always return success to prevent email enumeration
+ if (!$user) {
+ echo json_encode(['message' => 'If that email exists, a reset link has been sent']);
+ exit;
+ }
+
+ // Generate reset token
+ $resetToken = $auth->generateResetToken();
+ $hashedToken = hash('sha256', $resetToken);
+ $expiry = date('Y-m-d H:i:s', time() + 3600); // 1 hour
+
+ // Save token to database
+ $stmt = $db->prepare("UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?");
+ $stmt->bind_param("ssi", $hashedToken, $expiry, $user['id']);
+ $stmt->execute();
+
+ // Send email
+ Email::sendPasswordReset($email, $resetToken);
+
+ echo json_encode(['message' => 'If that email exists, a reset link has been sent']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/auth/login.php b/api/auth/login.php
new file mode 100644
index 0000000..9bd6a8a
--- /dev/null
+++ b/api/auth/login.php
@@ -0,0 +1,73 @@
+ 'Method not allowed']);
+ exit;
+}
+
+$data = json_decode(file_get_contents('php://input'), true);
+
+$email = trim($data['email'] ?? '');
+$password = $data['password'] ?? '';
+
+if (!filter_var($email, FILTER_VALIDATE_EMAIL) || empty($password)) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Invalid credentials']);
+ exit;
+}
+
+try {
+ $db = new Database();
+ $auth = new Auth();
+
+ // Find user
+ $stmt = $db->prepare("SELECT id, username, email, password, role FROM users WHERE email = ?");
+ $stmt->bind_param("s", $email);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ $user = $result->fetch_assoc();
+
+ if (!$user) {
+ http_response_code(401);
+ echo json_encode(['message' => 'Invalid credentials']);
+ exit;
+ }
+
+ // Verify password
+ if (!$auth->verifyPassword($password, $user['password'])) {
+ http_response_code(401);
+ echo json_encode(['message' => 'Invalid credentials']);
+ exit;
+ }
+
+ // Generate token
+ $token = $auth->generateToken($user['id']);
+
+ // Return user data (without password)
+ echo json_encode([
+ 'token' => $token,
+ 'user' => [
+ 'id' => $user['id'],
+ 'username' => $user['username'],
+ 'email' => $user['email'],
+ 'role' => $user['role']
+ ]
+ ]);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/auth/me.php b/api/auth/me.php
new file mode 100644
index 0000000..2239f7a
--- /dev/null
+++ b/api/auth/me.php
@@ -0,0 +1,36 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAuth();
+
+ echo json_encode([
+ 'user' => [
+ 'id' => $user['id'],
+ 'username' => $user['username'],
+ 'email' => $user['email'],
+ 'role' => $user['role']
+ ]
+ ]);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/auth/reset-password.php b/api/auth/reset-password.php
new file mode 100644
index 0000000..deb82a1
--- /dev/null
+++ b/api/auth/reset-password.php
@@ -0,0 +1,62 @@
+ 'Method not allowed']);
+ exit;
+}
+
+$data = json_decode(file_get_contents('php://input'), true);
+$token = $data['token'] ?? '';
+$password = $data['password'] ?? '';
+
+if (empty($token) || strlen($password) < 6) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Invalid request']);
+ exit;
+}
+
+try {
+ $db = new Database();
+ $auth = new Auth();
+
+ // Hash the token to compare
+ $hashedToken = hash('sha256', $token);
+
+ // Find user with valid token
+ $stmt = $db->prepare("SELECT id FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()");
+ $stmt->bind_param("s", $hashedToken);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ $user = $result->fetch_assoc();
+
+ if (!$user) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Invalid or expired token']);
+ exit;
+ }
+
+ // Update password
+ $hashedPassword = $auth->hashPassword($password);
+ $stmt = $db->prepare("UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE id = ?");
+ $stmt->bind_param("si", $hashedPassword, $user['id']);
+ $stmt->execute();
+
+ echo json_encode(['message' => 'Password reset successful']);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/auth/signup.php b/api/auth/signup.php
new file mode 100644
index 0000000..f4a3abe
--- /dev/null
+++ b/api/auth/signup.php
@@ -0,0 +1,96 @@
+ 'Method not allowed']);
+ exit;
+}
+
+$data = json_decode(file_get_contents('php://input'), true);
+
+// Validate input
+$username = trim($data['username'] ?? '');
+$email = trim($data['email'] ?? '');
+$password = $data['password'] ?? '';
+
+$errors = [];
+
+if (strlen($username) < 3) {
+ $errors[] = ['msg' => 'Username must be at least 3 characters'];
+}
+
+if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $errors[] = ['msg' => 'Please enter a valid email'];
+}
+
+if (strlen($password) < 6) {
+ $errors[] = ['msg' => 'Password must be at least 6 characters'];
+}
+
+if (!empty($errors)) {
+ http_response_code(400);
+ echo json_encode(['errors' => $errors]);
+ exit;
+}
+
+try {
+ $db = new Database();
+ $auth = new Auth();
+
+ // Check if user exists
+ $stmt = $db->prepare("SELECT id FROM users WHERE email = ? OR username = ?");
+ $stmt->bind_param("ss", $email, $username);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows > 0) {
+ http_response_code(400);
+ echo json_encode(['message' => 'User already exists']);
+ exit;
+ }
+
+ // Create user
+ $hashedPassword = $auth->hashPassword($password);
+ $stmt = $db->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
+ $stmt->bind_param("sss", $username, $email, $hashedPassword);
+
+ if (!$stmt->execute()) {
+ throw new Exception('Failed to create user');
+ }
+
+ $userId = $db->lastInsertId();
+
+ // Send welcome email
+ Email::sendWelcome($email, $username);
+
+ // Generate token
+ $token = $auth->generateToken($userId);
+
+ // Return user data
+ echo json_encode([
+ 'token' => $token,
+ 'user' => [
+ 'id' => $userId,
+ 'username' => $username,
+ 'email' => $email,
+ 'role' => 'user'
+ ]
+ ]);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/plugins/create.php b/api/plugins/create.php
new file mode 100644
index 0000000..1b4abc1
--- /dev/null
+++ b/api/plugins/create.php
@@ -0,0 +1,85 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $auth = new Auth();
+ $user = $auth->requireAuth();
+
+ $data = json_decode(file_get_contents('php://input'), true);
+
+ // Validate required fields
+ $name = trim($data['name'] ?? '');
+ $category = $data['category'] ?? '';
+ $description = trim($data['description'] ?? '');
+ $tag = trim($data['tag'] ?? '');
+
+ $validCategories = ['core', 'streaming', 'smarthome', 'control', 'creative'];
+
+ if (empty($name) || empty($category) || empty($description) || empty($tag)) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Missing required fields']);
+ exit;
+ }
+
+ if (!in_array($category, $validCategories)) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Invalid category']);
+ exit;
+ }
+
+ $db = new Database();
+
+ // Insert plugin
+ $author = $data['author'] ?? $user['username'];
+ $social_url = $data['socialUrl'] ?? $data['social_url'] ?? null;
+ $image_color = $data['imageColor'] ?? $data['image_color'] ?? 'from-blue-600 to-indigo-600';
+ $icon = $data['icon'] ?? 'fa-puzzle-piece';
+ $download_url = $data['downloadUrl'] ?? $data['download_url'] ?? null;
+ $instruction_url = $data['instructionUrl'] ?? $data['instruction_url'] ?? null;
+ $version = $data['version'] ?? '1.0.0';
+
+ $stmt = $db->prepare("INSERT INTO plugins (name, category, tag, author, author_id, social_url, description, image_color, icon, download_url, instruction_url, version, status)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')");
+
+ $stmt->bind_param("ssssisssssss", $name, $category, $tag, $author, $user['id'], $social_url, $description, $image_color, $icon, $download_url, $instruction_url, $version);
+
+ if (!$stmt->execute()) {
+ throw new Exception('Failed to create plugin');
+ }
+
+ $pluginId = $db->lastInsertId();
+
+ // Insert devices
+ $devices = $data['devices'] ?? ['Mixlar Mix'];
+ $stmt = $db->prepare("INSERT INTO plugin_devices (plugin_id, device_name) VALUES (?, ?)");
+ foreach ($devices as $device) {
+ $stmt->bind_param("is", $pluginId, $device);
+ $stmt->execute();
+ }
+
+ echo json_encode([
+ 'message' => 'Plugin submitted for review',
+ 'plugin' => ['id' => $pluginId]
+ ]);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/plugins/download.php b/api/plugins/download.php
new file mode 100644
index 0000000..3164839
--- /dev/null
+++ b/api/plugins/download.php
@@ -0,0 +1,54 @@
+ 'Method not allowed']);
+ exit;
+}
+
+$data = json_decode(file_get_contents('php://input'), true);
+$id = $data['id'] ?? $_GET['id'] ?? null;
+
+if (!$id) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Plugin ID required']);
+ exit;
+}
+
+try {
+ $db = new Database();
+
+ $stmt = $db->prepare("UPDATE plugins SET downloads = downloads + 1 WHERE id = ?");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+
+ if ($stmt->affected_rows === 0) {
+ http_response_code(404);
+ echo json_encode(['message' => 'Plugin not found']);
+ exit;
+ }
+
+ // Get updated download count
+ $stmt = $db->prepare("SELECT downloads FROM plugins WHERE id = ?");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ $plugin = $result->fetch_assoc();
+
+ echo json_encode(['downloads' => (int)$plugin['downloads']]);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/plugins/get.php b/api/plugins/get.php
new file mode 100644
index 0000000..70fef0f
--- /dev/null
+++ b/api/plugins/get.php
@@ -0,0 +1,56 @@
+ 'Method not allowed']);
+ exit;
+}
+
+$id = $_GET['id'] ?? null;
+
+if (!$id) {
+ http_response_code(400);
+ echo json_encode(['message' => 'Plugin ID required']);
+ exit;
+}
+
+try {
+ $db = new Database();
+
+ $stmt = $db->prepare("SELECT p.*, GROUP_CONCAT(pd.device_name) as devices
+ FROM plugins p
+ LEFT JOIN plugin_devices pd ON p.id = pd.plugin_id
+ WHERE p.id = ?
+ GROUP BY p.id");
+ $stmt->bind_param("i", $id);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ $plugin = $result->fetch_assoc();
+
+ if (!$plugin) {
+ http_response_code(404);
+ echo json_encode(['message' => 'Plugin not found']);
+ exit;
+ }
+
+ $plugin['devices'] = $plugin['devices'] ? explode(',', $plugin['devices']) : ['Mixlar Mix'];
+ $plugin['featured'] = (bool)$plugin['featured'];
+ $plugin['downloads'] = (int)$plugin['downloads'];
+
+ echo json_encode($plugin);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/api/plugins/list.php b/api/plugins/list.php
new file mode 100644
index 0000000..6dc2f00
--- /dev/null
+++ b/api/plugins/list.php
@@ -0,0 +1,78 @@
+ 'Method not allowed']);
+ exit;
+}
+
+try {
+ $db = new Database();
+
+ $category = $_GET['category'] ?? null;
+ $search = $_GET['search'] ?? null;
+ $featured = $_GET['featured'] ?? null;
+
+ $sql = "SELECT p.*, GROUP_CONCAT(pd.device_name) as devices
+ FROM plugins p
+ LEFT JOIN plugin_devices pd ON p.id = pd.plugin_id
+ WHERE p.status IN ('approved', 'instruction', 'download', 'installed')";
+
+ $params = [];
+ $types = '';
+
+ if ($category && $category !== 'all') {
+ $sql .= " AND p.category = ?";
+ $params[] = $category;
+ $types .= 's';
+ }
+
+ if ($featured === 'true') {
+ $sql .= " AND p.featured = 1";
+ }
+
+ if ($search) {
+ $sql .= " AND (p.name LIKE ? OR p.description LIKE ? OR p.tag LIKE ?)";
+ $searchTerm = "%$search%";
+ $params[] = $searchTerm;
+ $params[] = $searchTerm;
+ $params[] = $searchTerm;
+ $types .= 'sss';
+ }
+
+ $sql .= " GROUP BY p.id ORDER BY p.featured DESC, p.downloads DESC, p.created_at DESC";
+
+ if (!empty($params)) {
+ $stmt = $db->prepare($sql);
+ $stmt->bind_param($types, ...$params);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ } else {
+ $result = $db->query($sql);
+ }
+
+ $plugins = [];
+ while ($row = $result->fetch_assoc()) {
+ $row['devices'] = $row['devices'] ? explode(',', $row['devices']) : ['Mixlar Mix'];
+ $row['featured'] = (bool)$row['featured'];
+ $row['downloads'] = (int)$row['downloads'];
+ $plugins[] = $row;
+ }
+
+ echo json_encode($plugins);
+
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['message' => 'Server error']);
+ error_log($e->getMessage());
+}
diff --git a/app.js b/app.js
deleted file mode 100644
index f31328b..0000000
--- a/app.js
+++ /dev/null
@@ -1,165 +0,0 @@
-// Mixlar Marketplace - Main JavaScript
-
-let allPlugins = [];
-let currentFilter = 'all';
-let searchQuery = '';
-
-// Load plugins from list.json
-async function loadPlugins() {
- try {
- const response = await fetch('list.json');
- allPlugins = await response.json();
- updateTotalPlugins();
- renderPlugins();
- } catch (error) {
- console.error('Error loading plugins:', error);
- showError();
- }
-}
-
-// Update total plugins count
-function updateTotalPlugins() {
- const totalElement = document.getElementById('totalPlugins');
- if (totalElement) {
- totalElement.textContent = allPlugins.length;
- }
-}
-
-// Render plugins to the grid
-function renderPlugins() {
- const grid = document.getElementById('pluginsGrid');
- if (!grid) return;
-
- const filteredPlugins = filterPlugins();
-
- if (filteredPlugins.length === 0) {
- grid.innerHTML = `
-
-
-
No plugins found
-
Try adjusting your filters or search query
-
- `;
- return;
- }
-
- grid.innerHTML = filteredPlugins.map(plugin => createPluginCard(plugin)).join('');
-
- // Add click handlers
- document.querySelectorAll('.plugin-card').forEach(card => {
- card.addEventListener('click', () => {
- const pluginId = card.dataset.pluginId;
- window.location.href = `plugin.html?id=${pluginId}`;
- });
- });
-}
-
-// Filter plugins based on category and search
-function filterPlugins() {
- return allPlugins.filter(plugin => {
- const matchesCategory = currentFilter === 'all' || plugin.category === currentFilter;
- const matchesSearch = searchQuery === '' ||
- plugin.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
- plugin.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
- plugin.tag.toLowerCase().includes(searchQuery.toLowerCase());
-
- return matchesCategory && matchesSearch;
- });
-}
-
-// Create plugin card HTML
-function createPluginCard(plugin) {
- const statusClass = `status-${plugin.status}`;
- const statusLabel = plugin.status.charAt(0).toUpperCase() + plugin.status.slice(1);
-
- return `
-
-
-
- ${statusLabel}
-
-
-
-
${plugin.description}
-
-
-
- `;
-}
-
-// Convert Tailwind gradient classes to CSS gradient
-function getGradientColors(tailwindClass) {
- const gradientMap = {
- 'from-slate-700 to-slate-900': 'rgb(51, 65, 85), rgb(15, 23, 42)',
- 'from-blue-600 to-indigo-600': 'rgb(37, 99, 235), rgb(79, 70, 229)',
- 'from-gray-800 to-gray-950': 'rgb(31, 41, 55), rgb(3, 7, 18)',
- 'from-cyan-600 to-blue-700': 'rgb(8, 145, 178), rgb(29, 78, 216)',
- 'from-emerald-600 to-teal-700': 'rgb(5, 150, 105), rgb(15, 118, 110)',
- 'from-fuchsia-700 to-purple-800': 'rgb(162, 28, 175), rgb(107, 33, 168)',
- 'from-orange-600 to-amber-700': 'rgb(234, 88, 12), rgb(180, 83, 9)',
- };
-
- return gradientMap[tailwindClass] || 'rgb(99, 102, 241), rgb(139, 92, 246)';
-}
-
-// Show error message
-function showError() {
- const grid = document.getElementById('pluginsGrid');
- if (grid) {
- grid.innerHTML = `
-
-
-
Error loading plugins
-
Please try refreshing the page
-
- `;
- }
-}
-
-// Initialize filter buttons
-function initializeFilters() {
- const filterButtons = document.querySelectorAll('.filter-btn');
- filterButtons.forEach(button => {
- button.addEventListener('click', () => {
- // Update active state
- filterButtons.forEach(btn => btn.classList.remove('active'));
- button.classList.add('active');
-
- // Update filter and render
- currentFilter = button.dataset.category;
- renderPlugins();
- });
- });
-}
-
-// Initialize search
-function initializeSearch() {
- const searchInput = document.getElementById('searchInput');
- if (searchInput) {
- searchInput.addEventListener('input', (e) => {
- searchQuery = e.target.value;
- renderPlugins();
- });
- }
-}
-
-// Initialize the app
-document.addEventListener('DOMContentLoaded', () => {
- loadPlugins();
- initializeFilters();
- initializeSearch();
-});
diff --git a/config/config.example.php b/config/config.example.php
new file mode 100644
index 0000000..3b6ce40
--- /dev/null
+++ b/config/config.example.php
@@ -0,0 +1,32 @@
+
+
+
+
+
+ Admin Portal - Mixlar Marketplace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Plugin Name |
+ Category |
+ Author |
+ Status |
+ Downloads |
+ Actions |
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ | Username |
+ Email |
+ Role |
+ Joined |
+ Actions |
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/public/css/styles.css b/frontend/public/css/styles.css
new file mode 100644
index 0000000..c9a55f4
--- /dev/null
+++ b/frontend/public/css/styles.css
@@ -0,0 +1,737 @@
+:root {
+ --primary: #6366f1;
+ --primary-dark: #4f46e5;
+ --secondary: #8b5cf6;
+ --bg-dark: #0f172a;
+ --bg-darker: #020617;
+ --bg-card: #1e293b;
+ --text-primary: #f8fafc;
+ --text-secondary: #cbd5e1;
+ --text-muted: #64748b;
+ --border-color: #334155;
+ --success: #22c55e;
+ --warning: #f59e0b;
+ --danger: #ef4444;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ background: var(--bg-darker);
+ color: var(--text-primary);
+ line-height: 1.6;
+}
+
+.container {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 0 2rem;
+}
+
+/* Navbar */
+.navbar {
+ background: var(--bg-dark);
+ border-bottom: 1px solid var(--border-color);
+ padding: 1rem 0;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ backdrop-filter: blur(10px);
+}
+
+.nav-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.logo {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ cursor: pointer;
+}
+
+.logo i {
+ color: var(--primary);
+ font-size: 1.5rem;
+}
+
+.nav-links {
+ display: flex;
+ gap: 2rem;
+ align-items: center;
+}
+
+.nav-links a {
+ color: var(--text-secondary);
+ text-decoration: none;
+ transition: color 0.2s;
+ font-weight: 500;
+}
+
+.nav-links a:hover,
+.nav-links a.active {
+ color: var(--primary);
+}
+
+.nav-auth {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
+.btn {
+ padding: 0.625rem 1.25rem;
+ border-radius: 8px;
+ font-weight: 600;
+ text-decoration: none;
+ transition: all 0.2s;
+ border: none;
+ cursor: pointer;
+ font-size: 0.875rem;
+}
+
+.btn-primary {
+ background: var(--primary);
+ color: white;
+}
+
+.btn-primary:hover {
+ background: var(--primary-dark);
+ transform: translateY(-1px);
+}
+
+.btn-secondary {
+ background: transparent;
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+}
+
+.btn-secondary:hover {
+ background: var(--bg-card);
+}
+
+.btn-danger {
+ background: var(--danger);
+ color: white;
+}
+
+.btn-danger:hover {
+ background: #dc2626;
+}
+
+.btn-success {
+ background: var(--success);
+ color: white;
+}
+
+.btn-success:hover {
+ background: #16a34a;
+}
+
+/* Hero Section */
+.hero {
+ padding: 4rem 0;
+ text-align: center;
+ background: linear-gradient(135deg, var(--bg-dark) 0%, var(--bg-darker) 100%);
+}
+
+.hero h1 {
+ font-size: 3rem;
+ font-weight: 800;
+ margin-bottom: 1rem;
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.hero p {
+ font-size: 1.25rem;
+ color: var(--text-secondary);
+ margin-bottom: 2rem;
+}
+
+.stats {
+ display: flex;
+ justify-content: center;
+ gap: 3rem;
+ margin-top: 2rem;
+}
+
+.stat {
+ text-align: center;
+}
+
+.stat-number {
+ display: block;
+ font-size: 2.5rem;
+ font-weight: 800;
+ color: var(--primary);
+}
+
+.stat-label {
+ color: var(--text-muted);
+ font-size: 0.875rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+/* Filters */
+.filters {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin: 2rem 0;
+ gap: 2rem;
+ flex-wrap: wrap;
+}
+
+.filter-group {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ padding: 0.5rem 1rem;
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ color: var(--text-secondary);
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s;
+ font-weight: 500;
+ font-size: 0.875rem;
+}
+
+.filter-btn:hover {
+ border-color: var(--primary);
+ color: var(--primary);
+}
+
+.filter-btn.active {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+}
+
+.search-box {
+ position: relative;
+ flex: 1;
+ max-width: 400px;
+}
+
+.search-box i {
+ position: absolute;
+ left: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--text-muted);
+}
+
+.search-box input {
+ width: 100%;
+ padding: 0.75rem 1rem 0.75rem 2.75rem;
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ color: var(--text-primary);
+ font-size: 0.875rem;
+}
+
+.search-box input:focus {
+ outline: none;
+ border-color: var(--primary);
+}
+
+/* Plugins Grid */
+.plugins-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 1.5rem;
+ margin-bottom: 4rem;
+}
+
+.plugin-card {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ overflow: hidden;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.plugin-card:hover {
+ transform: translateY(-4px);
+ border-color: var(--primary);
+ box-shadow: 0 10px 30px rgba(99, 102, 241, 0.2);
+}
+
+.plugin-image {
+ height: 160px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.plugin-image i {
+ font-size: 4rem;
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.plugin-status {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ padding: 0.375rem 0.75rem;
+ border-radius: 6px;
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.status-instruction {
+ background: var(--warning);
+ color: white;
+}
+
+.status-download {
+ background: var(--success);
+ color: white;
+}
+
+.status-installed {
+ background: var(--text-muted);
+ color: white;
+}
+
+.status-pending {
+ background: var(--warning);
+ color: white;
+}
+
+.plugin-body {
+ padding: 1.5rem;
+}
+
+.plugin-title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+ color: var(--text-primary);
+}
+
+.plugin-meta {
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 0.75rem;
+}
+
+.tag {
+ background: var(--primary);
+ color: white;
+ padding: 0.25rem 0.625rem;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ font-weight: 600;
+}
+
+.version {
+ background: var(--bg-darker);
+ color: var(--text-muted);
+ padding: 0.25rem 0.625rem;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ font-weight: 600;
+}
+
+.plugin-description {
+ color: var(--text-secondary);
+ font-size: 0.875rem;
+ line-height: 1.5;
+ margin-bottom: 1rem;
+}
+
+.plugin-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-top: 1rem;
+ border-top: 1px solid var(--border-color);
+}
+
+.author {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--text-muted);
+ font-size: 0.875rem;
+}
+
+.view-details {
+ color: var(--primary);
+ font-weight: 600;
+ font-size: 0.875rem;
+}
+
+/* Auth Forms */
+.auth-container {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+}
+
+.auth-box {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: 16px;
+ padding: 3rem;
+ width: 100%;
+ max-width: 450px;
+}
+
+.auth-header {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.auth-header h1 {
+ font-size: 2rem;
+ margin-bottom: 0.5rem;
+}
+
+.auth-header p {
+ color: var(--text-secondary);
+}
+
+.form-group {
+ margin-bottom: 1.5rem;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: var(--text-secondary);
+ font-size: 0.875rem;
+}
+
+.form-group input,
+.form-group textarea,
+.form-group select {
+ width: 100%;
+ padding: 0.875rem;
+ background: var(--bg-darker);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ color: var(--text-primary);
+ font-size: 1rem;
+}
+
+.form-group input:focus,
+.form-group textarea:focus,
+.form-group select:focus {
+ outline: none;
+ border-color: var(--primary);
+}
+
+.form-group textarea {
+ resize: vertical;
+ min-height: 100px;
+}
+
+.error-message {
+ background: rgba(239, 68, 68, 0.1);
+ border: 1px solid var(--danger);
+ color: var(--danger);
+ padding: 0.75rem;
+ border-radius: 8px;
+ margin-bottom: 1rem;
+ font-size: 0.875rem;
+}
+
+.success-message {
+ background: rgba(34, 197, 94, 0.1);
+ border: 1px solid var(--success);
+ color: var(--success);
+ padding: 0.75rem;
+ border-radius: 8px;
+ margin-bottom: 1rem;
+ font-size: 0.875rem;
+}
+
+.auth-footer {
+ text-align: center;
+ margin-top: 2rem;
+ color: var(--text-secondary);
+ font-size: 0.875rem;
+}
+
+.auth-footer a {
+ color: var(--primary);
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.auth-footer a:hover {
+ text-decoration: underline;
+}
+
+/* Admin Panel */
+.admin-header {
+ background: var(--bg-dark);
+ border-bottom: 1px solid var(--border-color);
+ padding: 2rem 0;
+ margin-bottom: 2rem;
+}
+
+.admin-header h1 {
+ font-size: 2rem;
+ margin-bottom: 0.5rem;
+}
+
+.admin-stats {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 1.5rem;
+ margin-bottom: 3rem;
+}
+
+.stat-card {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ padding: 1.5rem;
+}
+
+.stat-card h3 {
+ color: var(--text-muted);
+ font-size: 0.875rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-bottom: 0.5rem;
+}
+
+.stat-card .number {
+ font-size: 2.5rem;
+ font-weight: 800;
+ color: var(--primary);
+}
+
+.admin-tabs {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 2rem;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.tab-btn {
+ padding: 0.75rem 1.5rem;
+ background: transparent;
+ border: none;
+ color: var(--text-secondary);
+ font-weight: 600;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ transition: all 0.2s;
+}
+
+.tab-btn:hover {
+ color: var(--primary);
+}
+
+.tab-btn.active {
+ color: var(--primary);
+ border-bottom-color: var(--primary);
+}
+
+.table-container {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ overflow: hidden;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: var(--bg-darker);
+}
+
+th {
+ padding: 1rem;
+ text-align: left;
+ font-weight: 600;
+ color: var(--text-muted);
+ font-size: 0.875rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+td {
+ padding: 1rem;
+ border-top: 1px solid var(--border-color);
+}
+
+tr:hover {
+ background: var(--bg-darker);
+}
+
+.actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.btn-sm {
+ padding: 0.375rem 0.75rem;
+ font-size: 0.75rem;
+}
+
+/* Footer */
+.footer {
+ background: var(--bg-dark);
+ border-top: 1px solid var(--border-color);
+ padding: 3rem 0 1.5rem;
+ margin-top: 4rem;
+}
+
+.footer-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 2rem;
+ margin-bottom: 2rem;
+}
+
+.footer-section h3,
+.footer-section h4 {
+ margin-bottom: 1rem;
+ color: var(--text-primary);
+}
+
+.footer-section p {
+ color: var(--text-secondary);
+ font-size: 0.875rem;
+}
+
+.footer-section a {
+ display: block;
+ color: var(--text-secondary);
+ text-decoration: none;
+ margin-bottom: 0.5rem;
+ font-size: 0.875rem;
+ transition: color 0.2s;
+}
+
+.footer-section a:hover {
+ color: var(--primary);
+}
+
+.footer-bottom {
+ text-align: center;
+ padding-top: 2rem;
+ border-top: 1px solid var(--border-color);
+ color: var(--text-muted);
+ font-size: 0.875rem;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .hero h1 {
+ font-size: 2rem;
+ }
+
+ .stats {
+ flex-direction: column;
+ gap: 1.5rem;
+ }
+
+ .filters {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .search-box {
+ max-width: 100%;
+ }
+
+ .plugins-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .nav-content {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .auth-box {
+ padding: 2rem;
+ }
+}
+
+/* Loading Spinner */
+.spinner {
+ border: 3px solid var(--border-color);
+ border-top: 3px solid var(--primary);
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 1s linear infinite;
+ margin: 2rem auto;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.hidden {
+ display: none !important;
+}
+
+.user-menu {
+ position: relative;
+}
+
+.user-info {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ cursor: pointer;
+ padding: 0.5rem 1rem;
+ border-radius: 8px;
+ transition: background 0.2s;
+}
+
+.user-info:hover {
+ background: var(--bg-card);
+}
+
+.user-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background: var(--primary);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ color: white;
+}
diff --git a/frontend/public/forgot-password.html b/frontend/public/forgot-password.html
new file mode 100644
index 0000000..074f3f2
--- /dev/null
+++ b/frontend/public/forgot-password.html
@@ -0,0 +1,80 @@
+
+
+
+
+
+ Forgot Password - Mixlar Marketplace
+
+
+
+
+
+
+
+
+
diff --git a/frontend/public/index.html b/frontend/public/index.html
new file mode 100644
index 0000000..1004bb3
--- /dev/null
+++ b/frontend/public/index.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+ Mixlar Marketplace - Plugins & Integrations
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/public/js/admin.js b/frontend/public/js/admin.js
new file mode 100644
index 0000000..919169a
--- /dev/null
+++ b/frontend/public/js/admin.js
@@ -0,0 +1,326 @@
+// Protect admin page
+document.addEventListener('DOMContentLoaded', () => {
+ requireAdmin();
+ loadStats();
+ loadPlugins();
+ loadUsers();
+ setupTabs();
+ setupFilters();
+ displayUserInfo();
+});
+
+// Display user info
+function displayUserInfo() {
+ const user = getCurrentUser();
+ if (user) {
+ document.getElementById('userAvatar').textContent = user.username.charAt(0).toUpperCase();
+ document.getElementById('userName').textContent = user.username;
+ }
+}
+
+// Load dashboard stats
+async function loadStats() {
+ try {
+ const response = await authenticatedFetch('/api/admin/stats.php');
+ const stats = await response.json();
+
+ document.getElementById('statTotalPlugins').textContent = stats.totalPlugins;
+ document.getElementById('statApprovedPlugins').textContent = stats.approvedPlugins;
+ document.getElementById('statPendingPlugins').textContent = stats.pendingPlugins;
+ document.getElementById('statTotalUsers').textContent = stats.totalUsers;
+ document.getElementById('statTotalDownloads').textContent = stats.totalDownloads.toLocaleString();
+ } catch (error) {
+ console.error('Error loading stats:', error);
+ }
+}
+
+// Load plugins
+async function loadPlugins() {
+ try {
+ const status = document.getElementById('statusFilter').value;
+ const category = document.getElementById('categoryFilter').value;
+
+ let url = '/api/admin/plugins.php?';
+ if (status !== 'all') url += `status=${status}&`;
+ if (category !== 'all') url += `category=${category}`;
+
+ const response = await authenticatedFetch(url);
+ const plugins = await response.json();
+
+ renderPluginsTable(plugins);
+ } catch (error) {
+ console.error('Error loading plugins:', error);
+ }
+}
+
+// Render plugins table
+function renderPluginsTable(plugins) {
+ const tbody = document.getElementById('pluginsTableBody');
+
+ if (plugins.length === 0) {
+ tbody.innerHTML = `
+
+ |
+ No plugins found
+ |
+
+ `;
+ return;
+ }
+
+ tbody.innerHTML = plugins.map(plugin => `
+
+
+ ${plugin.name}
+ v${plugin.version}
+ |
+ ${plugin.category} |
+
+ ${plugin.author}
+ ${plugin.authorId?.email || 'N/A'}
+ |
+ ${plugin.status} |
+ ${plugin.downloads || 0} |
+
+
+ ${plugin.status === 'pending' ? `
+
+
+ ` : ''}
+
+
+
+ |
+
+ `).join('');
+}
+
+// Load users
+async function loadUsers() {
+ try {
+ const response = await authenticatedFetch('/api/admin/users.php');
+ const users = await response.json();
+ renderUsersTable(users);
+ } catch (error) {
+ console.error('Error loading users:', error);
+ }
+}
+
+// Render users table
+function renderUsersTable(users) {
+ const tbody = document.getElementById('usersTableBody');
+
+ if (users.length === 0) {
+ tbody.innerHTML = `
+
+ |
+ No users found
+ |
+
+ `;
+ return;
+ }
+
+ tbody.innerHTML = users.map(user => `
+
+ |
+ ${user.username}
+ |
+ ${user.email} |
+
+
+ ${user.role}
+
+ |
+ ${new Date(user.created_at).toLocaleDateString()} |
+
+
+
+
+
+ |
+
+ `).join('');
+}
+
+// Approve plugin
+async function approvePlugin(pluginId) {
+ if (!confirm('Approve this plugin?')) return;
+
+ try {
+ await authenticatedFetch(`/api/admin/approve.php?id=${pluginId}`, {
+ method: 'PUT',
+ });
+ loadPlugins();
+ loadStats();
+ } catch (error) {
+ console.error('Error approving plugin:', error);
+ alert('Failed to approve plugin');
+ }
+}
+
+// Reject plugin
+async function rejectPlugin(pluginId) {
+ if (!confirm('Reject this plugin?')) return;
+
+ try {
+ await authenticatedFetch(`/api/admin/reject.php?id=${pluginId}`, {
+ method: 'PUT',
+ });
+ loadPlugins();
+ loadStats();
+ } catch (error) {
+ console.error('Error rejecting plugin:', error);
+ alert('Failed to reject plugin');
+ }
+}
+
+// Toggle feature status
+async function toggleFeature(pluginId, currentStatus) {
+ try {
+ await authenticatedFetch(`/api/admin/feature.php?id=${pluginId}`, {
+ method: 'PUT',
+ });
+ loadPlugins();
+ } catch (error) {
+ console.error('Error toggling feature:', error);
+ alert('Failed to update plugin');
+ }
+}
+
+// Delete plugin
+async function deletePlugin(pluginId) {
+ if (!confirm('Are you sure you want to delete this plugin? This action cannot be undone.')) return;
+
+ try {
+ await authenticatedFetch(`/api/admin/delete-plugin.php?id=${pluginId}`, {
+ method: 'DELETE',
+ });
+ loadPlugins();
+ loadStats();
+ } catch (error) {
+ console.error('Error deleting plugin:', error);
+ alert('Failed to delete plugin');
+ }
+}
+
+// Toggle user role
+async function toggleUserRole(userId, currentRole) {
+ const newRole = currentRole === 'admin' ? 'user' : 'admin';
+
+ if (!confirm(`Change user role to ${newRole}?`)) return;
+
+ try {
+ await authenticatedFetch(`/api/admin/change-role.php?id=${userId}`, {
+ method: 'PUT',
+ body: JSON.stringify({ role: newRole }),
+ });
+ loadUsers();
+ loadStats();
+ } catch (error) {
+ console.error('Error updating user role:', error);
+ alert('Failed to update user role');
+ }
+}
+
+// Delete user
+async function deleteUser(userId) {
+ if (!confirm('Are you sure you want to delete this user? This action cannot be undone.')) return;
+
+ try {
+ await authenticatedFetch(`/api/admin/delete-user.php?id=${userId}`, {
+ method: 'DELETE',
+ });
+ loadUsers();
+ loadStats();
+ } catch (error) {
+ console.error('Error deleting user:', error);
+ alert('Failed to delete user');
+ }
+}
+
+// Setup tabs
+function setupTabs() {
+ const tabs = document.querySelectorAll('.tab-btn');
+ tabs.forEach(tab => {
+ tab.addEventListener('click', () => {
+ const tabName = tab.dataset.tab;
+
+ // Update active tab
+ tabs.forEach(t => t.classList.remove('active'));
+ tab.classList.add('active');
+
+ // Show/hide content
+ document.getElementById('pluginsTab').classList.toggle('hidden', tabName !== 'plugins');
+ document.getElementById('usersTab').classList.toggle('hidden', tabName !== 'users');
+ });
+ });
+}
+
+// Setup filters
+function setupFilters() {
+ document.getElementById('statusFilter').addEventListener('change', loadPlugins);
+ document.getElementById('categoryFilter').addEventListener('change', loadPlugins);
+}
+
+// Helper functions
+function getCurrentUser() {
+ const userStr = localStorage.getItem('user');
+ return userStr ? JSON.parse(userStr) : null;
+}
+
+function logout() {
+ localStorage.removeItem('token');
+ localStorage.removeItem('user');
+ window.location.href = '/login.html';
+}
+
+function requireAdmin() {
+ const user = getCurrentUser();
+ if (!user || user.role !== 'admin') {
+ alert('Access denied. Admin only.');
+ window.location.href = '/';
+ }
+}
+
+async function authenticatedFetch(url, options = {}) {
+ const token = localStorage.getItem('token');
+
+ if (!token) {
+ window.location.href = '/login.html';
+ return;
+ }
+
+ const headers = {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`,
+ ...options.headers,
+ };
+
+ const response = await fetch(url, {
+ ...options,
+ headers,
+ });
+
+ if (response.status === 401) {
+ localStorage.removeItem('token');
+ localStorage.removeItem('user');
+ window.location.href = '/login.html';
+ return;
+ }
+
+ return response;
+}
diff --git a/frontend/public/js/auth.js b/frontend/public/js/auth.js
new file mode 100644
index 0000000..78aae6c
--- /dev/null
+++ b/frontend/public/js/auth.js
@@ -0,0 +1,63 @@
+// Check if user is authenticated
+function isAuthenticated() {
+ const token = localStorage.getItem('token');
+ return !!token;
+}
+
+// Get current user
+function getCurrentUser() {
+ const userStr = localStorage.getItem('user');
+ return userStr ? JSON.parse(userStr) : null;
+}
+
+// Logout
+function logout() {
+ localStorage.removeItem('token');
+ localStorage.removeItem('user');
+ window.location.href = '/login.html';
+}
+
+// Make authenticated API requests
+async function authenticatedFetch(url, options = {}) {
+ const token = localStorage.getItem('token');
+
+ if (!token) {
+ window.location.href = '/login.html';
+ return;
+ }
+
+ const headers = {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`,
+ ...options.headers,
+ };
+
+ const response = await fetch(url, {
+ ...options,
+ headers,
+ });
+
+ // If unauthorized, redirect to login
+ if (response.status === 401) {
+ localStorage.removeItem('token');
+ localStorage.removeItem('user');
+ window.location.href = '/login.html';
+ return;
+ }
+
+ return response;
+}
+
+// Protect admin pages
+function requireAuth() {
+ if (!isAuthenticated()) {
+ window.location.href = '/login.html';
+ }
+}
+
+function requireAdmin() {
+ const user = getCurrentUser();
+ if (!user || user.role !== 'admin') {
+ window.location.href = '/';
+ }
+}
diff --git a/frontend/public/js/marketplace.js b/frontend/public/js/marketplace.js
new file mode 100644
index 0000000..354e8d7
--- /dev/null
+++ b/frontend/public/js/marketplace.js
@@ -0,0 +1,232 @@
+let allPlugins = [];
+let currentFilter = 'all';
+let searchQuery = '';
+
+// Initialize the marketplace
+document.addEventListener('DOMContentLoaded', () => {
+ setupAuth();
+ loadPlugins();
+ initializeFilters();
+ initializeSearch();
+});
+
+// Setup authentication UI
+function setupAuth() {
+ const navAuth = document.querySelector('.nav-auth');
+ const user = getCurrentUser();
+
+ if (user) {
+ navAuth.innerHTML = `
+
+ ${user.role === 'admin' ? 'Admin' : ''}
+
+ `;
+ } else {
+ navAuth.innerHTML = `
+ Login
+ Sign Up
+ `;
+ }
+}
+
+// Load plugins from API
+async function loadPlugins() {
+ try {
+ const response = await fetch('/api/plugins/list.php');
+ allPlugins = await response.json();
+ updateStats();
+ renderPlugins();
+ } catch (error) {
+ console.error('Error loading plugins:', error);
+ showError();
+ }
+}
+
+// Update statistics
+function updateStats() {
+ const totalElement = document.getElementById('totalPlugins');
+ const downloadsElement = document.getElementById('totalDownloads');
+
+ if (totalElement) {
+ totalElement.textContent = allPlugins.length;
+ }
+
+ if (downloadsElement) {
+ const totalDownloads = allPlugins.reduce((sum, plugin) => sum + (plugin.downloads || 0), 0);
+ downloadsElement.textContent = totalDownloads.toLocaleString();
+ }
+}
+
+// Render plugins to the grid
+function renderPlugins() {
+ const grid = document.getElementById('pluginsGrid');
+ if (!grid) return;
+
+ const filteredPlugins = filterPlugins();
+
+ if (filteredPlugins.length === 0) {
+ grid.innerHTML = `
+
+
+
No plugins found
+
Try adjusting your filters or search query
+
+ `;
+ return;
+ }
+
+ grid.innerHTML = filteredPlugins.map(plugin => createPluginCard(plugin)).join('');
+
+ // Add click handlers
+ document.querySelectorAll('.plugin-card').forEach(card => {
+ card.addEventListener('click', () => {
+ const pluginId = card.dataset.pluginId;
+ showPluginDetail(pluginId);
+ });
+ });
+}
+
+// Filter plugins
+function filterPlugins() {
+ return allPlugins.filter(plugin => {
+ const matchesCategory = currentFilter === 'all' || plugin.category === currentFilter;
+ const matchesSearch = searchQuery === '' ||
+ plugin.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ plugin.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ plugin.tag.toLowerCase().includes(searchQuery.toLowerCase());
+
+ return matchesCategory && matchesSearch;
+ });
+}
+
+// Create plugin card HTML
+function createPluginCard(plugin) {
+ const statusClass = `status-${plugin.status}`;
+ const statusLabel = plugin.status.charAt(0).toUpperCase() + plugin.status.slice(1);
+
+ return `
+
+
+
+ ${statusLabel}
+
+
+
+
${plugin.description}
+
+
+
+ `;
+}
+
+// Convert gradient class to CSS
+function getGradientColors(tailwindClass) {
+ const gradientMap = {
+ 'from-slate-700 to-slate-900': 'rgb(51, 65, 85), rgb(15, 23, 42)',
+ 'from-blue-600 to-indigo-600': 'rgb(37, 99, 235), rgb(79, 70, 229)',
+ 'from-gray-800 to-gray-950': 'rgb(31, 41, 55), rgb(3, 7, 18)',
+ 'from-cyan-600 to-blue-700': 'rgb(8, 145, 178), rgb(29, 78, 216)',
+ 'from-emerald-600 to-teal-700': 'rgb(5, 150, 105), rgb(15, 118, 110)',
+ 'from-fuchsia-700 to-purple-800': 'rgb(162, 28, 175), rgb(107, 33, 168)',
+ 'from-orange-600 to-amber-700': 'rgb(234, 88, 12), rgb(180, 83, 9)',
+ };
+
+ return gradientMap[tailwindClass] || 'rgb(99, 102, 241), rgb(139, 92, 246)';
+}
+
+// Show plugin detail (simple alert for now)
+function showPluginDetail(pluginId) {
+ const plugin = allPlugins.find(p => p.id == pluginId);
+ if (!plugin) return;
+
+ let detailHtml = `
+
+
${plugin.name}
+
Category: ${plugin.category}
+
Version: ${plugin.version}
+
Author: ${plugin.author}
+
Description: ${plugin.description}
+
Downloads: ${plugin.downloads || 0}
+ `;
+
+ if (plugin.download_url || plugin.downloadUrl) {
+ detailHtml += `
Download
`;
+ }
+
+ if (plugin.instruction_url || plugin.instructionUrl) {
+ detailHtml += `
View Instructions
`;
+ }
+
+ detailHtml += '
';
+
+ alert(`Plugin: ${plugin.name}\n\nCategory: ${plugin.category}\nVersion: ${plugin.version}\nAuthor: ${plugin.author}\n\nDescription: ${plugin.description}`);
+}
+
+// Show error
+function showError() {
+ const grid = document.getElementById('pluginsGrid');
+ if (grid) {
+ grid.innerHTML = `
+
+
+
Error loading plugins
+
Please try refreshing the page
+
+ `;
+ }
+}
+
+// Initialize filters
+function initializeFilters() {
+ const filterButtons = document.querySelectorAll('.filter-btn');
+ filterButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ filterButtons.forEach(btn => btn.classList.remove('active'));
+ button.classList.add('active');
+ currentFilter = button.dataset.category;
+ renderPlugins();
+ });
+ });
+}
+
+// Initialize search
+function initializeSearch() {
+ const searchInput = document.getElementById('searchInput');
+ if (searchInput) {
+ searchInput.addEventListener('input', (e) => {
+ searchQuery = e.target.value;
+ renderPlugins();
+ });
+ }
+}
+
+// Helper functions from auth.js
+function getCurrentUser() {
+ const userStr = localStorage.getItem('user');
+ return userStr ? JSON.parse(userStr) : null;
+}
+
+function logout() {
+ localStorage.removeItem('token');
+ localStorage.removeItem('user');
+ window.location.href = '/frontend/public/login.html';
+}
diff --git a/frontend/public/login.html b/frontend/public/login.html
new file mode 100644
index 0000000..052a273
--- /dev/null
+++ b/frontend/public/login.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+ Login - Mixlar Marketplace
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/public/reset-password.html b/frontend/public/reset-password.html
new file mode 100644
index 0000000..7fc7ed4
--- /dev/null
+++ b/frontend/public/reset-password.html
@@ -0,0 +1,106 @@
+
+
+
+
+
+ Reset Password - Mixlar Marketplace
+
+
+
+
+
+
+
+
+
diff --git a/frontend/public/signup.html b/frontend/public/signup.html
new file mode 100644
index 0000000..d4bf42b
--- /dev/null
+++ b/frontend/public/signup.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+ Sign Up - Mixlar Marketplace
+
+
+
+
+
+
+
+
+
+
diff --git a/includes/Auth.php b/includes/Auth.php
new file mode 100644
index 0000000..683d1ec
--- /dev/null
+++ b/includes/Auth.php
@@ -0,0 +1,95 @@
+db = new Database();
+ }
+
+ // Verify JWT token from request
+ public function verifyToken() {
+ $headers = getallheaders();
+ $authHeader = isset($headers['Authorization']) ? $headers['Authorization'] :
+ (isset($headers['authorization']) ? $headers['authorization'] : null);
+
+ if (!$authHeader) {
+ return null;
+ }
+
+ $token = str_replace('Bearer ', '', $authHeader);
+ $payload = JWT::decode($token, JWT_SECRET);
+
+ if (!$payload) {
+ return null;
+ }
+
+ // Get user from database
+ $stmt = $this->db->prepare("SELECT id, username, email, role FROM users WHERE id = ?");
+ $stmt->bind_param("i", $payload['userId']);
+ $stmt->execute();
+ $result = $stmt->get_result();
+ $user = $result->fetch_assoc();
+
+ return $user;
+ }
+
+ // Generate JWT token
+ public function generateToken($userId) {
+ $payload = [
+ 'userId' => $userId,
+ 'exp' => time() + JWT_EXPIRY
+ ];
+
+ return JWT::encode($payload, JWT_SECRET);
+ }
+
+ // Hash password
+ public function hashPassword($password) {
+ return password_hash($password, PASSWORD_BCRYPT);
+ }
+
+ // Verify password
+ public function verifyPassword($password, $hash) {
+ return password_verify($password, $hash);
+ }
+
+ // Check if user is admin
+ public function isAdmin($user) {
+ return $user && $user['role'] === 'admin';
+ }
+
+ // Require authentication
+ public function requireAuth() {
+ $user = $this->verifyToken();
+
+ if (!$user) {
+ http_response_code(401);
+ echo json_encode(['message' => 'Unauthorized']);
+ exit;
+ }
+
+ return $user;
+ }
+
+ // Require admin role
+ public function requireAdmin() {
+ $user = $this->requireAuth();
+
+ if (!$this->isAdmin($user)) {
+ http_response_code(403);
+ echo json_encode(['message' => 'Access denied. Admin only.']);
+ exit;
+ }
+
+ return $user;
+ }
+
+ // Generate password reset token
+ public function generateResetToken() {
+ return bin2hex(random_bytes(32));
+ }
+}
diff --git a/includes/Database.php b/includes/Database.php
new file mode 100644
index 0000000..9d735c4
--- /dev/null
+++ b/includes/Database.php
@@ -0,0 +1,47 @@
+conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
+
+ if ($this->conn->connect_error) {
+ throw new Exception("Connection failed: " . $this->conn->connect_error);
+ }
+
+ $this->conn->set_charset("utf8mb4");
+ } catch (Exception $e) {
+ error_log("Database connection error: " . $e->getMessage());
+ throw $e;
+ }
+ }
+
+ public function getConnection() {
+ return $this->conn;
+ }
+
+ public function query($sql) {
+ return $this->conn->query($sql);
+ }
+
+ public function prepare($sql) {
+ return $this->conn->prepare($sql);
+ }
+
+ public function escapeString($string) {
+ return $this->conn->real_escape_string($string);
+ }
+
+ public function lastInsertId() {
+ return $this->conn->insert_id;
+ }
+
+ public function close() {
+ if ($this->conn) {
+ $this->conn->close();
+ }
+ }
+}
diff --git a/includes/Email.php b/includes/Email.php
new file mode 100644
index 0000000..2aed798
--- /dev/null
+++ b/includes/Email.php
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
Hi there,
+
You requested to reset your password for your Mixlar Marketplace account.
+
Click the button below to reset your password. This link will expire in 1 hour.
+
+
If you didn\'t request this, please ignore this email and your password will remain unchanged.
+
For security reasons, this link will expire in 1 hour.
+
+
+
+
+
+ ';
+
+ $headers = "MIME-Version: 1.0" . "\r\n";
+ $headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
+ $headers .= "From: " . EMAIL_FROM_NAME . " <" . EMAIL_FROM . ">" . "\r\n";
+
+ return mail($email, $subject, $message, $headers);
+ }
+
+ public static function sendWelcome($email, $username) {
+ $subject = "Welcome to Mixlar Marketplace";
+
+ $message = '
+
+
+
+
+
+
+
+
+
+
Hi ' . htmlspecialchars($username) . ',
+
Welcome to the Mixlar Plugin Marketplace! We\'re excited to have you join our community.
+
You can now:
+
+ - Browse and discover amazing plugins
+ - Submit your own plugins for approval
+ - Connect with other developers
+
+
+
+
+
+
+ ';
+
+ $headers = "MIME-Version: 1.0" . "\r\n";
+ $headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
+ $headers .= "From: " . EMAIL_FROM_NAME . " <" . EMAIL_FROM . ">" . "\r\n";
+
+ return mail($email, $subject, $message, $headers);
+ }
+}
diff --git a/includes/JWT.php b/includes/JWT.php
new file mode 100644
index 0000000..0b1a4c0
--- /dev/null
+++ b/includes/JWT.php
@@ -0,0 +1,51 @@
+ 'JWT', 'alg' => 'HS256']);
+ $payload = json_encode($payload);
+
+ $base64UrlHeader = self::base64UrlEncode($header);
+ $base64UrlPayload = self::base64UrlEncode($payload);
+
+ $signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
+ $base64UrlSignature = self::base64UrlEncode($signature);
+
+ return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
+ }
+
+ public static function decode($token, $secret) {
+ $parts = explode('.', $token);
+
+ if (count($parts) !== 3) {
+ return false;
+ }
+
+ list($base64UrlHeader, $base64UrlPayload, $base64UrlSignature) = $parts;
+
+ $signature = self::base64UrlDecode($base64UrlSignature);
+ $expectedSignature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
+
+ if (!hash_equals($signature, $expectedSignature)) {
+ return false;
+ }
+
+ $payload = json_decode(self::base64UrlDecode($base64UrlPayload), true);
+
+ // Check expiration
+ if (isset($payload['exp']) && $payload['exp'] < time()) {
+ return false;
+ }
+
+ return $payload;
+ }
+
+ private static function base64UrlEncode($data) {
+ return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
+ }
+
+ private static function base64UrlDecode($data) {
+ return base64_decode(strtr($data, '-_', '+/'));
+ }
+}
diff --git a/index.html b/index.html
index b8b585c..f891f13 100644
--- a/index.html
+++ b/index.html
@@ -3,68 +3,420 @@
- Mixlar Marketplace - Plugins & Integrations
-
+ Mixlar Marketplace - Extend Your Mixlar Experience
+
+