Crée pour moi le frontend complet d’une application web appelée BucketMaster.
Browse filesCette app permet à un utilisateur de :
créer un compte
créer des projets
créer des buckets dans chaque projet
envoyer, télécharger, visualiser et supprimer des fichiers
gérer les clés API
voir des dashboards simples sur l’espace utilisé
🎨 Charte graphique
Utilise les couleurs suivantes :
Jaune soleil (#F7D348)
Gris métallique (#6E6E6E)
Noir profond (#000000)
Blanc pur (#FFFFFF)
Style moderne, minimal, très propre, inspiré des dashboards pro (Linear, Supabase, Stripe).
🖥️ Pages à générer
Page Login / Register
Dashboard général
Page “Projects”
liste des projets
création d’un projet
Page “Project Detail”
liste des buckets
bouton “Créer un bucket”
Page “Bucket Detail”
upload de fichiers
aperçu des images
actions : télécharger, supprimer
Page “API Keys”
afficher, créer, supprimer une clé
Page “Settings”
🧠 Logique frontend
State global (store) pour : user, projects, buckets, files, API keys
Composants très modulaires
Système de notifications (success/error)
Composants réutilisables :
Button
Card
Modal
FileUploader
EmptyState
Table
NavBar + Sidebar
🪄 Attente
Génère :
les pages
les composants
la navigation
un design cohérent
une UI complète prête à connecter à un backend REST
- README.md +8 -5
- components/sidebar.js +25 -0
- index.html +58 -19
- scripts/main.js +93 -0
- scripts/router.js +232 -0
- scripts/store.js +88 -0
- styles/main.css +67 -0
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: BucketMaster - Cloud Storage Wizardry 🪄
|
| 3 |
+
colorFrom: red
|
| 4 |
+
colorTo: green
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CloudVaultSidebar extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
width: 280px;
|
| 9 |
+
background-color: white;
|
| 10 |
+
border-right: 1px solid #E4E7EB;
|
| 11 |
+
height: 100vh;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.sidebar-item {
|
| 15 |
+
transition: all 0.2s ease;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.sidebar-item:hover {
|
| 19 |
+
background-color: #FFF2CC;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.sidebar-item.active {
|
| 23 |
+
background-color: #FFE699;
|
| 24 |
+
border-left: 4px solid #FFC000;
|
| 25 |
+
}
|
|
@@ -1,19 +1,58 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>BucketMaster - Cloud Storage</title>
|
| 7 |
+
<link rel="stylesheet" href="styles/main.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script>
|
| 10 |
+
tailwind.config = {
|
| 11 |
+
theme: {
|
| 12 |
+
extend: {
|
| 13 |
+
colors: {
|
| 14 |
+
sun: '#F7D348',
|
| 15 |
+
metal: '#6E6E6E',
|
| 16 |
+
deep: '#000000',
|
| 17 |
+
pure: '#FFFFFF'
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
</script>
|
| 23 |
+
</head>
|
| 24 |
+
<body class="bg-pure text-deep min-h-screen">
|
| 25 |
+
<div id="app">
|
| 26 |
+
<!-- Auth pages will be rendered here initially -->
|
| 27 |
+
<div id="auth-container"></div>
|
| 28 |
+
|
| 29 |
+
<!-- Main app container (hidden until auth) -->
|
| 30 |
+
<div id="app-container" class="hidden">
|
| 31 |
+
<bucketmaster-navbar></bucketmaster-navbar>
|
| 32 |
+
<div class="flex">
|
| 33 |
+
<bucketmaster-sidebar></bucketmaster-sidebar>
|
| 34 |
+
<main class="flex-1 p-8">
|
| 35 |
+
<div id="page-content"></div>
|
| 36 |
+
</main>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<!-- Components -->
|
| 42 |
+
<script src="components/navbar.js"></script>
|
| 43 |
+
<script src="components/sidebar.js"></script>
|
| 44 |
+
<script src="components/button.js"></script>
|
| 45 |
+
<script src="components/card.js"></script>
|
| 46 |
+
<script src="components/modal.js"></script>
|
| 47 |
+
<script src="components/file-uploader.js"></script>
|
| 48 |
+
<script src="components/empty-state.js"></script>
|
| 49 |
+
<script src="components/table.js"></script>
|
| 50 |
+
<script src="components/notification.js"></script>
|
| 51 |
+
|
| 52 |
+
<!-- Scripts -->
|
| 53 |
+
<script src="scripts/store.js"></script>
|
| 54 |
+
<script src="scripts/router.js"></script>
|
| 55 |
+
<script src="scripts/main.js"></script>
|
| 56 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 57 |
+
</body>
|
| 58 |
+
</html>
|
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```javascript
|
| 2 |
+
import store from './store.js';
|
| 3 |
+
import router from './router.js';
|
| 4 |
+
|
| 5 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 6 |
+
// Initialize Feather Icons
|
| 7 |
+
if (window.feather) {
|
| 8 |
+
feather.replace();
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
// Check auth state
|
| 12 |
+
if (!store.state.user) {
|
| 13 |
+
window.location.hash = '#/login';
|
| 14 |
+
document.getElementById('app-container').classList.add('hidden');
|
| 15 |
+
document.getElementById('auth-container').classList.remove('hidden');
|
| 16 |
+
} else {
|
| 17 |
+
document.getElementById('app-container').classList.remove('hidden');
|
| 18 |
+
document.getElementById('auth-container').classList.add('hidden');
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
// Set up event listeners
|
| 22 |
+
document.addEventListener('click', (e) => {
|
| 23 |
+
// Handle project clicks
|
| 24 |
+
if (e.target.closest('[data-project-id]')) {
|
| 25 |
+
const projectId = e.target.closest('[data-project-id]').getAttribute('data-project-id');
|
| 26 |
+
window.location.hash = `#/projects/${projectId}`;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Handle bucket clicks
|
| 30 |
+
if (e.target.closest('[data-bucket-id]')) {
|
| 31 |
+
const bucketId = e.target.closest('[data-bucket-id]').getAttribute('data-bucket-id');
|
| 32 |
+
window.location.hash = `#/buckets/${bucketId}`;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
// Handle file actions
|
| 36 |
+
if (e.target.closest('[data-file-id]')) {
|
| 37 |
+
const fileId = e.target.closest('[data-file-id]').getAttribute('data-file-id');
|
| 38 |
+
const action = e.target.closest('[data-file-id]').getAttribute('data-action');
|
| 39 |
+
|
| 40 |
+
if (action === 'download') {
|
| 41 |
+
store.addNotification({
|
| 42 |
+
id: Date.now(),
|
| 43 |
+
type: 'success',
|
| 44 |
+
message: 'Download started'
|
| 45 |
+
});
|
| 46 |
+
} else if (action === 'delete') {
|
| 47 |
+
store.removeFile(fileId);
|
| 48 |
+
store.addNotification({
|
| 49 |
+
id: Date.now(),
|
| 50 |
+
type: 'success',
|
| 51 |
+
message: 'File deleted'
|
| 52 |
+
});
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Handle API key deletion
|
| 57 |
+
if (e.target.closest('[data-key-id]')) {
|
| 58 |
+
const keyId = e.target.closest('[data-key-id]').getAttribute('data-key-id');
|
| 59 |
+
store.removeApiKey(keyId);
|
| 60 |
+
store.addNotification({
|
| 61 |
+
id: Date.now(),
|
| 62 |
+
type: 'success',
|
| 63 |
+
message: 'API key deleted'
|
| 64 |
+
});
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
// Handle create project button
|
| 68 |
+
if (e.target.closest('#create-project-btn')) {
|
| 69 |
+
const modal = document.createElement('bucketmaster-modal');
|
| 70 |
+
modal.setAttribute('title', 'Create Project');
|
| 71 |
+
modal.innerHTML = `
|
| 72 |
+
<form id="create-project-form" class="space-y-4">
|
| 73 |
+
<div>
|
| 74 |
+
<label class="block text-metal mb-1">Project Name</label>
|
| 75 |
+
<input type="text" required class="w-full px-3 py-2 border border-metal-200 rounded">
|
| 76 |
+
</div>
|
| 77 |
+
<div class="pt-2">
|
| 78 |
+
<bucketmaster-button type="submit">Create Project</bucketmaster-button>
|
| 79 |
+
</div>
|
| 80 |
+
</form>
|
| 81 |
+
`;
|
| 82 |
+
document.body.appendChild(modal);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// Handle create bucket button
|
| 86 |
+
if (e.target.closest('#create-bucket-btn')) {
|
| 87 |
+
const modal = document.createElement('bucketmaster-modal');
|
| 88 |
+
modal.setAttribute('title', 'Create Bucket');
|
| 89 |
+
modal.innerHTML = `
|
| 90 |
+
<form id="create-bucket-form" class="space-y-4">
|
| 91 |
+
<div>
|
| 92 |
+
<label class="block text-metal mb-1">Bucket Name</label>
|
| 93 |
+
<
|
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class Router {
|
| 2 |
+
constructor() {
|
| 3 |
+
this.routes = {
|
| 4 |
+
'/login': this.renderLogin,
|
| 5 |
+
'/register': this.renderRegister,
|
| 6 |
+
'/dashboard': this.renderDashboard,
|
| 7 |
+
'/projects': this.renderProjects,
|
| 8 |
+
'/projects/:id': this.renderProjectDetail,
|
| 9 |
+
'/buckets/:id': this.renderBucketDetail,
|
| 10 |
+
'/api-keys': this.renderApiKeys,
|
| 11 |
+
'/settings': this.renderSettings
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
window.addEventListener('hashchange', () => this.handleRoute());
|
| 15 |
+
this.handleRoute();
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
handleRoute() {
|
| 19 |
+
const path = window.location.hash.substring(1) || '/dashboard';
|
| 20 |
+
const route = Object.keys(this.routes).find(r => {
|
| 21 |
+
if (r.includes(':')) {
|
| 22 |
+
const basePath = r.split('/:')[0];
|
| 23 |
+
return path.startsWith(basePath);
|
| 24 |
+
}
|
| 25 |
+
return r === path;
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
if (route) {
|
| 29 |
+
this.routes[route]();
|
| 30 |
+
} else {
|
| 31 |
+
this.renderNotFound();
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
renderLogin() {
|
| 36 |
+
document.getElementById('auth-container').innerHTML = `
|
| 37 |
+
<bucketmaster-auth type="login"></bucketmaster-auth>
|
| 38 |
+
`;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
renderRegister() {
|
| 42 |
+
document.getElementById('auth-container').innerHTML = `
|
| 43 |
+
<bucketmaster-auth type="register"></bucketmaster-auth>
|
| 44 |
+
`;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
renderDashboard() {
|
| 48 |
+
document.getElementById('page-content').innerHTML = `
|
| 49 |
+
<h1 class="text-2xl font-bold mb-6">Dashboard</h1>
|
| 50 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 51 |
+
<bucketmaster-card title="Projects" value="${store.state.projects.length}" icon="folder"></bucketmaster-card>
|
| 52 |
+
<bucketmaster-card title="Buckets" value="${store.state.buckets.length}" icon="database"></bucketmaster-card>
|
| 53 |
+
<bucketmaster-card title="Storage Used" value="2.5 GB" icon="hard-drive"></bucketmaster-card>
|
| 54 |
+
</div>
|
| 55 |
+
`;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
renderProjects() {
|
| 59 |
+
document.getElementById('page-content').innerHTML = `
|
| 60 |
+
<div class="flex justify-between items-center mb-6">
|
| 61 |
+
<h1 class="text-2xl font-bold">Projects</h1>
|
| 62 |
+
<bucketmaster-button id="create-project-btn" icon="plus">New Project</bucketmaster-button>
|
| 63 |
+
</div>
|
| 64 |
+
${store.state.projects.length > 0 ? `
|
| 65 |
+
<bucketmaster-table>
|
| 66 |
+
${store.state.projects.map(project => `
|
| 67 |
+
<tr class="hover:bg-sun/10 transition-200 cursor-pointer" data-project-id="${project.id}">
|
| 68 |
+
<td>${project.name}</td>
|
| 69 |
+
<td>${project.buckets.length} buckets</td>
|
| 70 |
+
<td>${new Date(project.createdAt).toLocaleDateString()}</td>
|
| 71 |
+
</tr>
|
| 72 |
+
`).join('')}
|
| 73 |
+
</bucketmaster-table>
|
| 74 |
+
` : `
|
| 75 |
+
<bucketmaster-empty-state
|
| 76 |
+
title="No projects yet"
|
| 77 |
+
description="Get started by creating your first project"
|
| 78 |
+
action-text="Create Project"
|
| 79 |
+
action-id="create-project-btn"
|
| 80 |
+
icon="folder-plus">
|
| 81 |
+
</bucketmaster-empty-state>
|
| 82 |
+
`}
|
| 83 |
+
`;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
renderProjectDetail() {
|
| 87 |
+
const projectId = window.location.hash.split('/')[2];
|
| 88 |
+
store.setCurrentProject(projectId);
|
| 89 |
+
|
| 90 |
+
document.getElementById('page-content').innerHTML = `
|
| 91 |
+
<div class="flex justify-between items-center mb-6">
|
| 92 |
+
<h1 class="text-2xl font-bold">${store.state.currentProject?.name || 'Project'}</h1>
|
| 93 |
+
<bucketmaster-button id="create-bucket-btn" icon="plus">New Bucket</bucketmaster-button>
|
| 94 |
+
</div>
|
| 95 |
+
${store.state.currentProject?.buckets.length > 0 ? `
|
| 96 |
+
<bucketmaster-table>
|
| 97 |
+
${store.state.currentProject.buckets.map(bucket => `
|
| 98 |
+
<tr class="hover:bg-sun/10 transition-200 cursor-pointer" data-bucket-id="${bucket.id}">
|
| 99 |
+
<td>${bucket.name}</td>
|
| 100 |
+
<td>${bucket.files.length} files</td>
|
| 101 |
+
<td>${bucket.size}</td>
|
| 102 |
+
<td>${new Date(bucket.createdAt).toLocaleDateString()}</td>
|
| 103 |
+
</tr>
|
| 104 |
+
`).join('')}
|
| 105 |
+
</bucketmaster-table>
|
| 106 |
+
` : `
|
| 107 |
+
<bucketmaster-empty-state
|
| 108 |
+
title="No buckets yet"
|
| 109 |
+
description="Create your first bucket to start uploading files"
|
| 110 |
+
action-text="Create Bucket"
|
| 111 |
+
action-id="create-bucket-btn"
|
| 112 |
+
icon="database">
|
| 113 |
+
</bucketmaster-empty-state>
|
| 114 |
+
`}
|
| 115 |
+
`;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
renderBucketDetail() {
|
| 119 |
+
const bucketId = window.location.hash.split('/')[2];
|
| 120 |
+
store.setCurrentBucket(bucketId);
|
| 121 |
+
|
| 122 |
+
document.getElementById('page-content').innerHTML = `
|
| 123 |
+
<div class="flex justify-between items-center mb-6">
|
| 124 |
+
<h1 class="text-2xl font-bold">${store.state.currentBucket?.name || 'Bucket'}</h1>
|
| 125 |
+
<bucketmaster-button id="upload-file-btn" icon="upload">Upload File</bucketmaster-button>
|
| 126 |
+
</div>
|
| 127 |
+
${store.state.currentBucket?.files.length > 0 ? `
|
| 128 |
+
<bucketmaster-table>
|
| 129 |
+
${store.state.currentBucket.files.map(file => `
|
| 130 |
+
<tr>
|
| 131 |
+
<td class="flex items-center">
|
| 132 |
+
<i data-feather="${file.icon}" class="file-icon file-icon-${file.type.toLowerCase()}"></i>
|
| 133 |
+
${file.name}
|
| 134 |
+
</td>
|
| 135 |
+
<td>${file.size}</td>
|
| 136 |
+
<td>${file.type}</td>
|
| 137 |
+
<td>${new Date(file.uploadedAt).toLocaleDateString()}</td>
|
| 138 |
+
<td class="text-right">
|
| 139 |
+
<div class="flex space-x-2">
|
| 140 |
+
<button class="text-metal hover:text-sun transition-200" data-file-id="${file.id}" data-action="download">
|
| 141 |
+
<i data-feather="download"></i>
|
| 142 |
+
</button>
|
| 143 |
+
<button class="text-metal hover:text-red-500 transition-200" data-file-id="${file.id}" data-action="delete">
|
| 144 |
+
<i data-feather="trash-2"></i>
|
| 145 |
+
</button>
|
| 146 |
+
</div>
|
| 147 |
+
</td>
|
| 148 |
+
</tr>
|
| 149 |
+
`).join('')}
|
| 150 |
+
</bucketmaster-table>
|
| 151 |
+
` : `
|
| 152 |
+
<bucketmaster-empty-state
|
| 153 |
+
title="No files yet"
|
| 154 |
+
description="Upload your first file to this bucket"
|
| 155 |
+
action-text="Upload File"
|
| 156 |
+
action-id="upload-file-btn"
|
| 157 |
+
icon="file">
|
| 158 |
+
</bucketmaster-empty-state>
|
| 159 |
+
`}
|
| 160 |
+
`;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
renderApiKeys() {
|
| 164 |
+
document.getElementById('page-content').innerHTML = `
|
| 165 |
+
<div class="flex justify-between items-center mb-6">
|
| 166 |
+
<h1 class="text-2xl font-bold">API Keys</h1>
|
| 167 |
+
<bucketmaster-button id="create-api-key-btn" icon="plus">New API Key</bucketmaster-button>
|
| 168 |
+
</div>
|
| 169 |
+
${store.state.apiKeys.length > 0 ? `
|
| 170 |
+
<bucketmaster-table>
|
| 171 |
+
${store.state.apiKeys.map(key => `
|
| 172 |
+
<tr>
|
| 173 |
+
<td>${key.name}</td>
|
| 174 |
+
<td>${key.key.substring(0, 8)}...</td>
|
| 175 |
+
<td>${new Date(key.createdAt).toLocaleDateString()}</td>
|
| 176 |
+
<td class="text-right">
|
| 177 |
+
<button class="text-metal hover:text-red-500 transition-200" data-key-id="${key.id}">
|
| 178 |
+
<i data-feather="trash-2"></i>
|
| 179 |
+
</button>
|
| 180 |
+
</td>
|
| 181 |
+
</tr>
|
| 182 |
+
`).join('')}
|
| 183 |
+
</bucketmaster-table>
|
| 184 |
+
` : `
|
| 185 |
+
<bucketmaster-empty-state
|
| 186 |
+
title="No API Keys"
|
| 187 |
+
description="Create your first API key to access the BucketMaster API"
|
| 188 |
+
action-text="Create API Key"
|
| 189 |
+
action-id="create-api-key-btn"
|
| 190 |
+
icon="key">
|
| 191 |
+
</bucketmaster-empty-state>
|
| 192 |
+
`}
|
| 193 |
+
`;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
renderSettings() {
|
| 197 |
+
document.getElementById('page-content').innerHTML = `
|
| 198 |
+
<h1 class="text-2xl font-bold mb-6">Settings</h1>
|
| 199 |
+
<div class="bg-pure rounded-lg shadow p-6 max-w-2xl">
|
| 200 |
+
<h2 class="text-xl font-semibold mb-4">Account</h2>
|
| 201 |
+
<form id="account-form" class="space-y-4">
|
| 202 |
+
<div>
|
| 203 |
+
<label class="block text-metal mb-1">Name</label>
|
| 204 |
+
<input type="text" value="${store.state.user?.name || ''}" class="w-full px-3 py-2 border border-metal-200 rounded">
|
| 205 |
+
</div>
|
| 206 |
+
<div>
|
| 207 |
+
<label class="block text-metal mb-1">Email</label>
|
| 208 |
+
<input type="email" value="${store.state.user?.email || ''}" class="w-full px-3 py-2 border border-metal-200 rounded">
|
| 209 |
+
</div>
|
| 210 |
+
<div class="pt-4">
|
| 211 |
+
<bucketmaster-button type="submit">Save Changes</bucketmaster-button>
|
| 212 |
+
</div>
|
| 213 |
+
</form>
|
| 214 |
+
</div>
|
| 215 |
+
`;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
renderNotFound() {
|
| 219 |
+
document.getElementById('page-content').innerHTML = `
|
| 220 |
+
<bucketmaster-empty-state
|
| 221 |
+
title="Page Not Found"
|
| 222 |
+
description="The page you're looking for doesn't exist"
|
| 223 |
+
action-text="Go to Dashboard"
|
| 224 |
+
action-id="go-to-dashboard"
|
| 225 |
+
icon="alert-circle">
|
| 226 |
+
</bucketmaster-empty-state>
|
| 227 |
+
`;
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
const router = new Router();
|
| 232 |
+
export default router;
|
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class Store {
|
| 2 |
+
constructor() {
|
| 3 |
+
this.state = {
|
| 4 |
+
user: null,
|
| 5 |
+
projects: [],
|
| 6 |
+
currentProject: null,
|
| 7 |
+
buckets: [],
|
| 8 |
+
currentBucket: null,
|
| 9 |
+
files: [],
|
| 10 |
+
apiKeys: [],
|
| 11 |
+
notifications: []
|
| 12 |
+
};
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
setUser(user) {
|
| 16 |
+
this.state.user = user;
|
| 17 |
+
this.save();
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
addProject(project) {
|
| 21 |
+
this.state.projects.push(project);
|
| 22 |
+
this.save();
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
setCurrentProject(projectId) {
|
| 26 |
+
this.state.currentProject = this.state.projects.find(p => p.id === projectId);
|
| 27 |
+
this.save();
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
addBucket(bucket) {
|
| 31 |
+
this.state.buckets.push(bucket);
|
| 32 |
+
this.save();
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
setCurrentBucket(bucketId) {
|
| 36 |
+
this.state.currentBucket = this.state.buckets.find(b => b.id === bucketId);
|
| 37 |
+
this.save();
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
addFile(file) {
|
| 41 |
+
this.state.files.push(file);
|
| 42 |
+
this.save();
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
removeFile(fileId) {
|
| 46 |
+
this.state.files = this.state.files.filter(f => f.id !== fileId);
|
| 47 |
+
this.save();
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
addApiKey(apiKey) {
|
| 51 |
+
this.state.apiKeys.push(apiKey);
|
| 52 |
+
this.save();
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
removeApiKey(keyId) {
|
| 56 |
+
this.state.apiKeys = this.state.apiKeys.filter(k => k.id !== keyId);
|
| 57 |
+
this.save();
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
addNotification(notification) {
|
| 61 |
+
this.state.notifications.push(notification);
|
| 62 |
+
setTimeout(() => {
|
| 63 |
+
this.removeNotification(notification.id);
|
| 64 |
+
}, 3000);
|
| 65 |
+
this.save();
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
removeNotification(id) {
|
| 69 |
+
this.state.notifications = this.state.notifications.filter(n => n.id !== id);
|
| 70 |
+
this.save();
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
save() {
|
| 74 |
+
localStorage.setItem('bucketmaster-state', JSON.stringify(this.state));
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
load() {
|
| 78 |
+
const saved = localStorage.getItem('bucketmaster-state');
|
| 79 |
+
if (saved) {
|
| 80 |
+
this.state = JSON.parse(saved);
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
const store = new Store();
|
| 86 |
+
store.load();
|
| 87 |
+
|
| 88 |
+
export default store;
|
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import 'tailwindcss/base';
|
| 2 |
+
@import 'tailwindcss/components';
|
| 3 |
+
@import 'tailwindcss/utilities';
|
| 4 |
+
|
| 5 |
+
/* Base styles */
|
| 6 |
+
body {
|
| 7 |
+
font-family: 'Inter', sans-serif;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
/* Custom scrollbar */
|
| 11 |
+
::-webkit-scrollbar {
|
| 12 |
+
width: 6px;
|
| 13 |
+
height: 6px;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
::-webkit-scrollbar-track {
|
| 17 |
+
background: #f1f1f1;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
::-webkit-scrollbar-thumb {
|
| 21 |
+
background: #F7D348;
|
| 22 |
+
border-radius: 3px;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
/* Animations */
|
| 26 |
+
@keyframes fadeIn {
|
| 27 |
+
from { opacity: 0; }
|
| 28 |
+
to { opacity: 1; }
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.fade-in {
|
| 32 |
+
animation: fadeIn 0.3s ease;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
/* Transitions */
|
| 36 |
+
.transition-200 {
|
| 37 |
+
transition: all 0.2s ease;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/* File icons */
|
| 41 |
+
.file-icon {
|
| 42 |
+
@apply w-5 h-5 mr-2;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.file-icon-image {
|
| 46 |
+
color: #38A169;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.file-icon-pdf {
|
| 50 |
+
color: #E53E3E;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.file-icon-video {
|
| 54 |
+
color: #805AD5;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.file-icon-audio {
|
| 58 |
+
color: #3182CE;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.file-icon-zip {
|
| 62 |
+
color: #DD6B20;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.file-icon-default {
|
| 66 |
+
color: #6E6E6E;
|
| 67 |
+
}
|