+
+ ✨ {{ t('hero.newVersion') }} 2.0
+
+
- {{ displayText }}
- |
+ {{ t('hero.titleStart') }}
+ Companion
-
{{ t('hero.subtitle') }}
+
+
+ {{ t('hero.subtitle') }}
+
-
- {{ t('hero.exploreFeatures') }}
+
+ {{ t('hero.inviteBot') }} →
-
- {{ t('hero.inviteBot') }}
+
+ {{ t('hero.exploreFeatures') }}
@@ -22,10 +29,12 @@
{{ stats.servers }}+
{{ t('hero.servers') }}
+
{{ stats.users }}+
{{ t('hero.users') }}
+
{{ stats.commands }}+
{{ t('hero.commands') }}
@@ -34,17 +43,93 @@
-
-
🤝
-
{{ t('hero.feature1') }}
-
-
-
🎫
-
{{ t('hero.feature2') }}
-
-
-
⚙️
-
{{ t('hero.feature3') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Now Playing
+
Neon Nights - Synthwave Mix
+
+
+ 1:24
+ 3:45
+
+
+
+
+
+
+
+
+
+
+ 🔥
+ High Quality Audio
+
+
+
+ 🛡️
+ Advanced Security
+
@@ -52,24 +137,11 @@
diff --git a/AmayoWeb/src/components/ServerSelector.vue b/AmayoWeb/src/components/ServerSelector.vue
new file mode 100644
index 0000000..1a9c352
--- /dev/null
+++ b/AmayoWeb/src/components/ServerSelector.vue
@@ -0,0 +1,524 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ server.name }}
+
+
+ Online
+
+
+
+
+ Enter Realm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmayoWeb/src/components/TechButton.vue b/AmayoWeb/src/components/TechButton.vue
new file mode 100644
index 0000000..1d49244
--- /dev/null
+++ b/AmayoWeb/src/components/TechButton.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmayoWeb/src/components/TechCard.vue b/AmayoWeb/src/components/TechCard.vue
new file mode 100644
index 0000000..058be01
--- /dev/null
+++ b/AmayoWeb/src/components/TechCard.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
diff --git a/AmayoWeb/src/components/TheWelcome.vue b/AmayoWeb/src/components/TheWelcome.vue
deleted file mode 100644
index 41aaa4a..0000000
--- a/AmayoWeb/src/components/TheWelcome.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
-
-
- Documentation
-
- Vue’s
- official documentation
- provides you with all information you need to get started.
-
-
-
-
-
-
- Tooling
-
- This project is served and bundled with
- Vite . The
- recommended IDE setup is
- VSCode
- +
- Vue - Official . If you need to test your components and web pages, check out
- Vitest
- and
- Cypress
- /
- Playwright .
-
-
-
- More instructions are available in
- README.md .
-
-
-
-
-
-
- Ecosystem
-
- Get official tools and libraries for your project:
- Pinia ,
- Vue Router ,
- Vue Test Utils , and
- Vue Dev Tools . If
- you need more resources, we suggest paying
- Awesome Vue
- a visit.
-
-
-
-
-
-
- Community
-
- Got stuck? Ask your question on
- Vue Land
- (our official Discord server), or
- StackOverflow . You should also follow the official
- @vuejs.org
- Bluesky account or the
- @vuejs
- X account for latest news in the Vue world.
-
-
-
-
-
-
- Support Vue
-
- As an independent project, Vue relies on community backing for its sustainability. You can help
- us by
- becoming a sponsor .
-
-
diff --git a/AmayoWeb/src/components/WelcomeItem.vue b/AmayoWeb/src/components/WelcomeItem.vue
deleted file mode 100644
index d3bc3a3..0000000
--- a/AmayoWeb/src/components/WelcomeItem.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
diff --git a/AmayoWeb/src/components/docs/HeroSection.vue b/AmayoWeb/src/components/docs/HeroSection.vue
deleted file mode 100644
index 41bf5e8..0000000
--- a/AmayoWeb/src/components/docs/HeroSection.vue
+++ /dev/null
@@ -1,213 +0,0 @@
-
-
-
-
-
- {{ titleText }}
-
-
{{ t('hero_docs.subtitle') }}
-
-
-
- {{ t('hero_docs.exploreFeatures') }}
-
-
- {{ t('hero_docs.inviteBot') }}
-
-
-
-
-
-
-
-
-
-
diff --git a/AmayoWeb/src/components/docs/IslandNavbar.vue b/AmayoWeb/src/components/docs/IslandNavbar.vue
deleted file mode 100644
index 140ca61..0000000
--- a/AmayoWeb/src/components/docs/IslandNavbar.vue
+++ /dev/null
@@ -1,340 +0,0 @@
-
-
-
-
-
-
-
-
-
{{ botName }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/AmayoWeb/src/composables/useTheme.js b/AmayoWeb/src/composables/useTheme.js
deleted file mode 100644
index 6edeb6b..0000000
--- a/AmayoWeb/src/composables/useTheme.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import { ref, watch } from 'vue'
-
-const themes = {
- red: {
- primary: '#ff1744',
- secondary: '#d50000',
- accent: '#ff5252',
- gradient: 'linear-gradient(135deg, #ff1744, #d50000)',
- glow: 'rgba(255, 23, 68, 0.5)',
- },
- blue: {
- primary: '#2196f3',
- secondary: '#1565c0',
- accent: '#64b5f6',
- gradient: 'linear-gradient(135deg, #2196f3, #1565c0)',
- glow: 'rgba(33, 150, 243, 0.5)',
- },
- green: {
- primary: '#00e676',
- secondary: '#00c853',
- accent: '#69f0ae',
- gradient: 'linear-gradient(135deg, #00e676, #00c853)',
- glow: 'rgba(0, 230, 118, 0.5)',
- },
- purple: {
- primary: '#e040fb',
- secondary: '#9c27b0',
- accent: '#ea80fc',
- gradient: 'linear-gradient(135deg, #e040fb, #9c27b0)',
- glow: 'rgba(224, 64, 251, 0.5)',
- },
- orange: {
- primary: '#ff9100',
- secondary: '#ff6d00',
- accent: '#ffab40',
- gradient: 'linear-gradient(135deg, #ff9100, #ff6d00)',
- glow: 'rgba(255, 145, 0, 0.5)',
- },
-}
-
-const currentTheme = ref('red')
-
-const applyTheme = (themeName) => {
- const theme = themes[themeName]
- if (!theme) return
-
- const root = document.documentElement
-
- // Aplicar variables CSS
- root.style.setProperty('--color-primary', theme.primary)
- root.style.setProperty('--color-secondary', theme.secondary)
- root.style.setProperty('--color-accent', theme.accent)
- root.style.setProperty('--gradient-primary', theme.gradient)
- root.style.setProperty('--color-glow', theme.glow)
-
- // Aplicar data attribute para el tema
- root.setAttribute('data-theme', themeName)
-
- console.log('Theme applied:', themeName, theme)
-}
-
-export function useTheme() {
- const setTheme = (themeName) => {
- if (themes[themeName]) {
- currentTheme.value = themeName
- applyTheme(themeName)
- localStorage.setItem('theme', themeName)
- }
- }
-
- const initTheme = () => {
- const savedTheme = localStorage.getItem('theme')
- if (savedTheme && themes[savedTheme]) {
- currentTheme.value = savedTheme
- }
- applyTheme(currentTheme.value)
- }
-
- watch(currentTheme, (newTheme) => {
- applyTheme(newTheme)
- })
-
- return {
- currentTheme,
- themes,
- setTheme,
- initTheme,
- }
-}
diff --git a/AmayoWeb/src/i18n/locales.js b/AmayoWeb/src/i18n/locales.js
index 0b875a8..c4cfb64 100644
--- a/AmayoWeb/src/i18n/locales.js
+++ b/AmayoWeb/src/i18n/locales.js
@@ -3,8 +3,11 @@ export default {
navbar: {
getStarted: 'Comenzar',
dashboard: 'Panel',
+ premium: 'Premium',
},
hero: {
+ titleStart: 'Tu Compañero',
+ newVersion: 'Nueva Versión',
subtitle: 'Transforma tu servidor de Discord en una experiencia de Ultima Generacion de comandos con nuevas tecnologias.',
exploreFeatures: 'Explorar Características',
inviteBot: 'Invitar Bot',
@@ -15,6 +18,30 @@ export default {
feature2: 'Tickets',
feature3: 'AutoMod',
},
+ features: {
+ title: 'Todo lo que necesitas',
+ subtitle: 'Amayo viene cargado con todas las funciones para llevar tu servidor al siguiente nivel.',
+ ai: {
+ title: 'Inteligencia Artificial',
+ desc: 'Recomendaciones inteligentes y respuestas potenciadas por IA.'
+ },
+ music: {
+ title: 'Música de Alta Calidad',
+ desc: 'Reproduce música sin lag desde Spotify, YouTube, SoundCloud y más con filtros de audio premium.'
+ },
+ alliances: {
+ title: 'Alianzas',
+ desc: 'Gestiona alianzas con otros servidores para crecer juntos.'
+ },
+ web: {
+ title: 'Panel Web',
+ desc: 'Configura todo desde un panel de control fácil de usar y moderno.'
+ },
+ embedding: {
+ title: 'Mejor Embedding',
+ desc: 'Crea mensajes visuales impactantes con nuestro constructor de embeds.'
+ }
+ },
hero_docs: {
subtitle: 'En esta seccion esta la documentacion oficial para Amayo Bot.',
exploreFeatures: 'Ver Comandos',
@@ -58,14 +85,32 @@ export default {
green: 'Verde',
purple: 'Púrpura',
orange: 'Naranja',
+ },
+ premium: {
+ title: 'Elige tu Nivel',
+ subtitle: 'Desbloquea todo el potencial de Amayo con nuestros planes premium.',
+ get: 'Obtener',
+ month: 'Mensual',
+ personal: 'Uso Personal',
+ boost1: '1 Boost Server',
+ boost2: '2 Boost Server',
+ features: {
+ volumeBoost: 'Volumen Boost',
+ betterRecs: 'Mejores Recomendaciones',
+ playlistLimit: 'Mayor Numero de canciones a importar en la playlist 100>200',
+ tier1Rewards: 'Recompensas del 1 boost',
+ }
}
},
en: {
navbar: {
getStarted: 'Get Started',
dashboard: 'Dashboard',
+ premium: 'Premium',
},
hero: {
+ titleStart: 'Your Music',
+ newVersion: 'New Version',
subtitle: 'Transform your Discord server into a Next-Gen command experience with cutting-edge technologies.',
exploreFeatures: 'Explore Features',
inviteBot: 'Invite Bot',
@@ -76,7 +121,31 @@ export default {
feature2: 'Tickets',
feature3: 'AutoMod',
},
- hero_docs: {
+ features: {
+ title: 'Everything you need',
+ subtitle: 'Amayo comes loaded with all the features to take your server to the next level.',
+ ai: {
+ title: 'Artificial Intelligence',
+ desc: 'Smart recommendations and AI-powered responses.'
+ },
+ music: {
+ title: 'High Quality Music',
+ desc: 'Play lag-free music from Spotify, YouTube, SoundCloud and more with premium audio filters.'
+ },
+ alliances: {
+ title: 'Alliances',
+ desc: 'Manage alliances with other servers to grow together.'
+ },
+ web: {
+ title: 'Web Dashboard',
+ desc: 'Configure everything from an easy-to-use and modern control panel.'
+ },
+ embedding: {
+ title: 'Better Embedding',
+ desc: 'Create stunning visual messages with our embed builder.'
+ }
+ },
+ hero_docs: {
subtitle: 'This section contains the official documentation for Amayo Bot.',
exploreFeatures: 'View Commands',
inviteBot: 'View Functions',
@@ -119,6 +188,21 @@ export default {
green: 'Green',
purple: 'Purple',
orange: 'Orange',
+ },
+ premium: {
+ title: 'Choose Your Tier',
+ subtitle: 'Unlock the full potential of Amayo with our premium plans.',
+ get: 'Get',
+ month: 'Monthly',
+ personal: 'Personal Use',
+ boost1: '1 Boost Server',
+ boost2: '2 Boost Server',
+ features: {
+ volumeBoost: 'Volume Boost',
+ betterRecs: 'Better Recommendations',
+ playlistLimit: 'Higher song import limit 100>200',
+ tier1Rewards: '1 Boost Rewards',
+ }
}
}
}
diff --git a/AmayoWeb/src/router/index.js b/AmayoWeb/src/router/index.js
index 6fe8c0e..71c4eaa 100644
--- a/AmayoWeb/src/router/index.js
+++ b/AmayoWeb/src/router/index.js
@@ -9,11 +9,6 @@ const router = createRouter({
name: 'home',
component: () => import('../views/HomeView.vue')
},
- {
- path: '/docs',
- name: 'docs',
- component: () => import('../views/DocsView.vue')
- },
{
path: '/auth/callback',
name: 'auth-callback',
@@ -28,6 +23,30 @@ const router = createRouter({
path: '/privacy',
name: 'privacy',
component: () => import('../views/PrivacyPolicy.vue')
+ },
+ {
+ path: '/premium',
+ name: 'premium',
+ component: () => import('../views/PremiumView.vue')
+ },
+ {
+ path: '/dash/:guildId',
+ name: 'dashboard',
+ component: () => import('../views/DashboardView.vue')
+ },
+ {
+ path: '/dash/:guildId/settings',
+ name: 'settings',
+ component: () => import('../views/SettingsView.vue')
+ },
+ {
+ path: '/login',
+ name: 'login',
+ component: () => import('../views/LoginView.vue')
+ },
+ {
+ path: '/dashboard',
+ redirect: '/dash/me' // Temporary redirect until guild selection is implemented
}
]
})
@@ -35,7 +54,7 @@ const router = createRouter({
// Navigation guard para rutas protegidas
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('authToken')
-
+
if (to.meta.requiresAuth && !token) {
next('/')
} else {
diff --git a/AmayoWeb/src/services/auth.js b/AmayoWeb/src/services/auth.js
index 5644fd3..95ad4e6 100644
--- a/AmayoWeb/src/services/auth.js
+++ b/AmayoWeb/src/services/auth.js
@@ -1,5 +1,5 @@
import axios from 'axios'
-import { securityService, rateLimiter } from './security'
+import { securityService } from './security'
// Inicializar servicio de seguridad
await securityService.initialize().catch(err => {
@@ -9,11 +9,12 @@ await securityService.initialize().catch(err => {
// Crear instancia de axios con configuración de seguridad
const createSecureAxios = () => {
const instance = axios.create({
- timeout: 10000, // 10 segundos timeout
+ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
+ timeout: 10000,
+ withCredentials: true, // Importante para enviar cookies de sesión
headers: securityService.getSecurityHeaders()
})
- // Interceptor para agregar headers de seguridad
instance.interceptors.request.use(
config => {
config.headers = {
@@ -25,11 +26,9 @@ const createSecureAxios = () => {
error => Promise.reject(error)
)
- // Interceptor para validar respuestas
instance.interceptors.response.use(
response => securityService.validateResponse(response),
error => {
- // Manejar errores de forma segura
if (error.response?.status === 429) {
console.error('Rate limit exceeded')
}
@@ -40,141 +39,47 @@ const createSecureAxios = () => {
return instance
}
-const secureAxios = createSecureAxios()
-
-// No exponer la URL directamente - usar el servicio de seguridad
-const getApiUrl = (path) => {
- try {
- const baseUrl = securityService.getApiEndpoint()
- return `${baseUrl}${path}`
- } catch (error) {
- console.error('Failed to get API URL:', error)
- throw new Error('API service unavailable')
- }
-}
+export const secureAxios = createSecureAxios()
export const authService = {
- // Redirigir al usuario a Discord OAuth2
- loginWithDiscord() {
- // Rate limiting para prevenir abuso
- if (!rateLimiter.canMakeRequest('/auth/discord', 'auth')) {
- const remainingTime = Math.ceil(rateLimiter.getRemainingTime('/auth/discord', 'auth') / 1000)
- throw new Error(`Too many login attempts. Please wait ${remainingTime} seconds.`)
- }
+ // El login se hace directamente con el link href="/auth/discord" en LoginView
+ // El proxy de Vite redirige /auth/* a localhost:3000
- const clientId = import.meta.env.VITE_DISCORD_CLIENT_ID
- if (!clientId) {
- throw new Error('Discord client ID not configured')
- }
-
- const redirectUri = import.meta.env.PROD
- ? window.location.origin + '/auth/callback'
- : 'http://localhost:5173/auth/callback'
-
- const scope = 'identify guilds'
- const state = securityService.generateSessionToken() // CSRF protection
-
- // Guardar state para validación
- sessionStorage.setItem('oauth_state', state)
-
- const authUrl = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}&state=${state}`
-
- window.location.href = authUrl
- },
-
- // Intercambiar código por token
- async handleCallback(code, state) {
- // Validar state para prevenir CSRF
- const savedState = sessionStorage.getItem('oauth_state')
- if (state !== savedState) {
- throw new Error('Invalid OAuth state - possible CSRF attack')
- }
- sessionStorage.removeItem('oauth_state')
-
- // Rate limiting
- if (!rateLimiter.canMakeRequest('/auth/callback', 'auth')) {
- throw new Error('Too many authentication attempts')
- }
-
- try {
- const response = await secureAxios.post(
- getApiUrl('/auth/discord/callback'),
- { code, state }
- )
-
- const { token, user } = response.data
-
- if (!token || !user) {
- throw new Error('Invalid authentication response')
- }
-
- // Guardar token de forma segura
- localStorage.setItem('authToken', token)
- localStorage.setItem('user', JSON.stringify(user))
-
- return { token, user }
- } catch (error) {
- console.error('Authentication error:', error)
- throw new Error('Authentication failed')
- }
- },
-
- // Obtener usuario actual
+ // Obtener información de sesión actual (del backend via proxy)
async getCurrentUser() {
- const token = localStorage.getItem('authToken')
- if (!token) return null
-
- // Rate limiting
- if (!rateLimiter.canMakeRequest('/auth/me', 'api')) {
- throw new Error('Too many requests')
- }
-
try {
- const response = await secureAxios.get(getApiUrl('/auth/me'))
- return response.data
- } catch (error) {
- console.error('Error fetching user:', error)
-
- // Si el token es inválido, hacer logout
- if (error.response?.status === 401) {
- this.logout()
+ const response = await secureAxios.get('/api/session')
+ if (response.data && response.data.user) {
+ localStorage.setItem('user', JSON.stringify(response.data.user))
+ return response.data.user
}
-
+ return null
+ } catch (error) {
+ console.error('Error fetching session:', error)
+ return null
+ }
+ },
+
+ // Verificar autenticación (basado en sesión)
+ isAuthenticated() {
+ const user = localStorage.getItem('user')
+ return !!user
+ },
+
+ // Obtener usuario cacheado
+ getCachedUser() {
+ try {
+ const user = localStorage.getItem('user')
+ return user ? JSON.parse(user) : null
+ } catch {
return null
}
},
// Logout
logout() {
- localStorage.removeItem('authToken')
localStorage.removeItem('user')
- securityService.clearSensitiveData()
- window.location.href = '/'
- },
-
- // Verificar si el usuario está autenticado
- isAuthenticated() {
- const token = localStorage.getItem('authToken')
- if (!token) return false
-
- // Validar que el token no esté expirado (básico)
- try {
- const payload = JSON.parse(atob(token.split('.')[1]))
- const isExpired = payload.exp && payload.exp * 1000 < Date.now()
-
- if (isExpired) {
- this.logout()
- return false
- }
-
- return true
- } catch {
- return !!token // Fallback si no se puede decodificar
- }
- },
-
- // Obtener token
- getToken() {
- return localStorage.getItem('authToken')
+ // Redirigir a la ruta de logout que será proxiada
+ window.location.href = '/auth/logout'
}
}
diff --git a/AmayoWeb/src/services/bot.js b/AmayoWeb/src/services/bot.js
index 9147be6..a32f8f7 100644
--- a/AmayoWeb/src/services/bot.js
+++ b/AmayoWeb/src/services/bot.js
@@ -9,6 +9,7 @@ await securityService.initialize().catch(err => {
// Crear instancia de axios con configuración de seguridad
const createSecureAxios = () => {
const instance = axios.create({
+ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
timeout: 10000,
headers: securityService.getSecurityHeaders()
})
@@ -54,15 +55,15 @@ export const botService = {
}
try {
- const response = await secureAxios.get(getApiUrl('/bot/stats'))
-
+ const response = await secureAxios.get(getApiUrl('/api/bot/stats'))
+
// Cachear los resultados
this.cacheStats(response.data)
-
+
return response.data
} catch (error) {
console.error('Error fetching bot stats:', error)
-
+
// Retornar stats cacheadas si falló la petición
return this.getCachedStats() || {
servers: 0,
@@ -80,11 +81,11 @@ export const botService = {
}
try {
- const response = await secureAxios.get(getApiUrl('/bot/info'))
-
+ const response = await secureAxios.get(getApiUrl('/api/bot/info'))
+
// Cachear info del bot
this.cacheBotInfo(response.data)
-
+
return response.data
} catch (error) {
console.error('Error fetching bot info:', error)
diff --git a/AmayoWeb/src/services/embeds.js b/AmayoWeb/src/services/embeds.js
new file mode 100644
index 0000000..0013533
--- /dev/null
+++ b/AmayoWeb/src/services/embeds.js
@@ -0,0 +1,33 @@
+import { secureAxios } from './auth'
+
+export const embedsService = {
+ // Get all embeds for a guild
+ async getEmbeds(guildId) {
+ const response = await secureAxios.get(`/api/guilds/${guildId}/embeds`)
+ return response.data
+ },
+
+ // Get a specific embed
+ async getEmbed(guildId, embedId) {
+ const response = await secureAxios.get(`/api/guilds/${guildId}/embeds/${embedId}`)
+ return response.data
+ },
+
+ // Create a new embed
+ async createEmbed(guildId, data) {
+ const response = await secureAxios.post(`/api/guilds/${guildId}/embeds`, data)
+ return response.data
+ },
+
+ // Update an embed
+ async updateEmbed(guildId, embedId, data) {
+ const response = await secureAxios.put(`/api/guilds/${guildId}/embeds/${embedId}`, data)
+ return response.data
+ },
+
+ // Delete an embed
+ async deleteEmbed(guildId, embedId) {
+ await secureAxios.delete(`/api/guilds/${guildId}/embeds/${embedId}`)
+ return true
+ }
+}
diff --git a/AmayoWeb/src/services/guilds.js b/AmayoWeb/src/services/guilds.js
new file mode 100644
index 0000000..3d7d12e
--- /dev/null
+++ b/AmayoWeb/src/services/guilds.js
@@ -0,0 +1,290 @@
+import axios from 'axios'
+import { securityService, rateLimiter } from './security'
+
+// Crear instancia de axios con configuración de seguridad
+const createSecureAxios = () => {
+ const instance = axios.create({
+ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
+ timeout: 10000,
+ withCredentials: true, // Importante para enviar cookies
+ headers: securityService.getSecurityHeaders()
+ })
+
+ instance.interceptors.request.use(
+ config => {
+ config.headers = {
+ ...config.headers,
+ ...securityService.getSecurityHeaders()
+ }
+ return config
+ },
+ error => Promise.reject(error)
+ )
+
+ instance.interceptors.response.use(
+ response => securityService.validateResponse(response),
+ error => {
+ if (error.response?.status === 429) {
+ console.error('Rate limit exceeded')
+ }
+ return Promise.reject(error)
+ }
+ )
+
+ return instance
+}
+
+const secureAxios = createSecureAxios()
+
+const getApiUrl = (path) => {
+ try {
+ const baseUrl = securityService.getApiEndpoint()
+ return `${baseUrl}${path}`
+ } catch (error) {
+ console.error('Failed to get API URL:', error)
+ throw new Error('API service unavailable')
+ }
+}
+
+export const guildService = {
+ // Obtener servidores del usuario desde la sesión (via proxy)
+ async getUserGuilds() {
+ // Rate limiting
+ if (!rateLimiter.canMakeRequest('/api/session', 'api')) {
+ return this.getCachedGuilds() || []
+ }
+
+ try {
+ const response = await secureAxios.get('/api/session')
+
+ if (response.data && response.data.guilds) {
+ this.cacheGuilds(response.data.guilds)
+ return response.data.guilds
+ }
+
+ return []
+ } catch (error) {
+ console.error('Error fetching user guilds:', error)
+ return this.getCachedGuilds() || []
+ }
+ },
+
+ // Cache system
+ cacheGuilds(guilds) {
+ try {
+ const cacheData = {
+ data: guilds,
+ timestamp: Date.now(),
+ expiresIn: 5 * 60 * 1000 // 5 minutes
+ }
+ sessionStorage.setItem('user_guilds_cache', JSON.stringify(cacheData))
+ } catch (error) {
+ console.error('Failed to cache guilds:', error)
+ }
+ },
+
+ getCachedGuilds() {
+ try {
+ const cached = sessionStorage.getItem('user_guilds_cache')
+ if (!cached) return null
+
+ const cacheData = JSON.parse(cached)
+ const isExpired = Date.now() - cacheData.timestamp > cacheData.expiresIn
+
+ if (isExpired) {
+ sessionStorage.removeItem('user_guilds_cache')
+ return null
+ }
+
+ return cacheData.data
+ } catch (error) {
+ console.error('Failed to get cached guilds:', error)
+ return null
+ }
+ },
+
+ // Get guild statistics
+ async getGuildStats(guildId) {
+ if (!rateLimiter.canMakeRequest(`/api/guild/${guildId}/stats`, 'api')) {
+ return this.getCachedGuildStats(guildId) || null
+ }
+
+ try {
+ const response = await secureAxios.get(`/api/guild/${guildId}/stats`)
+ if (response.data) {
+ this.cacheGuildStats(guildId, response.data)
+ return response.data
+ }
+ return null
+ } catch (error) {
+ console.error('Error fetching guild stats:', error)
+ return this.getCachedGuildStats(guildId) || null
+ }
+ },
+
+ // Get recent guild actions
+ async getGuildActions(guildId) {
+ if (!rateLimiter.canMakeRequest(`/api/guild/${guildId}/actions`, 'api')) {
+ return this.getCachedGuildActions(guildId) || []
+ }
+
+ try {
+ const response = await secureAxios.get(`/api/guild/${guildId}/actions`)
+ if (response.data && response.data.actions) {
+ this.cacheGuildActions(guildId, response.data.actions)
+ return response.data.actions
+ }
+ return []
+ } catch (error) {
+ console.error('Error fetching guild actions:', error)
+ return this.getCachedGuildActions(guildId) || []
+ }
+ },
+
+ // Cache guild stats
+ cacheGuildStats(guildId, stats) {
+ try {
+ const cacheData = {
+ data: stats,
+ timestamp: Date.now(),
+ expiresIn: 30 * 1000 // 30 seconds
+ }
+ sessionStorage.setItem(`guild_stats_${guildId}`, JSON.stringify(cacheData))
+ } catch (error) {
+ console.error('Failed to cache guild stats:', error)
+ }
+ },
+
+ getCachedGuildStats(guildId) {
+ try {
+ const cached = sessionStorage.getItem(`guild_stats_${guildId}`)
+ if (!cached) return null
+
+ const cacheData = JSON.parse(cached)
+ const isExpired = Date.now() - cacheData.timestamp > cacheData.expiresIn
+
+ if (isExpired) {
+ sessionStorage.removeItem(`guild_stats_${guildId}`)
+ return null
+ }
+
+ return cacheData.data
+ } catch (error) {
+ console.error('Failed to get cached guild stats:', error)
+ return null
+ }
+ },
+
+ // Cache guild actions
+ cacheGuildActions(guildId, actions) {
+ try {
+ const cacheData = {
+ data: actions,
+ timestamp: Date.now(),
+ expiresIn: 15 * 1000 // 15 seconds
+ }
+ sessionStorage.setItem(`guild_actions_${guildId}`, JSON.stringify(cacheData))
+ } catch (error) {
+ console.error('Failed to cache guild actions:', error)
+ }
+ },
+
+ getCachedGuildActions(guildId) {
+ try {
+ const cached = sessionStorage.getItem(`guild_actions_${guildId}`)
+ if (!cached) return null
+
+ const cacheData = JSON.parse(cached)
+ const isExpired = Date.now() - cacheData.timestamp > cacheData.expiresIn
+
+ if (isExpired) {
+ sessionStorage.removeItem(`guild_actions_${guildId}`)
+ return null
+ }
+
+ return cacheData.data
+ } catch (error) {
+ console.error('Failed to get cached guild actions:', error)
+ return null
+ }
+ },
+
+ // Get guild settings
+ async getGuildSettings(guildId) {
+ if (!rateLimiter.canMakeRequest(`/api/guild/${guildId}/settings`, 'api')) {
+ return this.getCachedGuildSettings(guildId) || null
+ }
+
+ try {
+ const response = await secureAxios.get(`/api/guild/${guildId}/settings`)
+ if (response.data) {
+ this.cacheGuildSettings(guildId, response.data)
+ return response.data
+ }
+ return null
+ } catch (error) {
+ console.error('Error fetching guild settings:', error)
+ return this.getCachedGuildSettings(guildId) || null
+ }
+ },
+
+ // Update guild settings
+ async updateGuildSettings(guildId, settings) {
+ try {
+ const response = await secureAxios.patch(`/api/guild/${guildId}/settings`, settings)
+ if (response.data && response.data.settings) {
+ this.cacheGuildSettings(guildId, response.data.settings)
+ return response.data
+ }
+ return null
+ } catch (error) {
+ console.error('Error updating guild settings:', error)
+ throw error
+ }
+ },
+
+ // Cache guild settings
+ cacheGuildSettings(guildId, settings) {
+ try {
+ const cacheData = {
+ data: settings,
+ timestamp: Date.now(),
+ expiresIn: 60 * 1000 // 1 minute
+ }
+ sessionStorage.setItem(`guild_settings_${guildId}`, JSON.stringify(cacheData))
+ } catch (error) {
+ console.error('Failed to cache guild settings:', error)
+ }
+ },
+
+ getCachedGuildSettings(guildId) {
+ try {
+ const cached = sessionStorage.getItem(`guild_settings_${guildId}`)
+ if (!cached) return null
+
+ const cacheData = JSON.parse(cached)
+ const isExpired = Date.now() - cacheData.timestamp > cacheData.expiresIn
+
+ if (isExpired) {
+ sessionStorage.removeItem(`guild_settings_${guildId}`)
+ return null
+ }
+
+ return cacheData.data
+ } catch (error) {
+ console.error('Failed to get cached guild settings:', error)
+ return null
+ }
+ },
+
+ // Get guild roles
+ async getGuildRoles(guildId) {
+ try {
+ const response = await secureAxios.get(`/api/guild/${guildId}/roles`)
+ return response.data || []
+ } catch (error) {
+ console.error('Error fetching guild roles:', error)
+ return []
+ }
+ }
+}
diff --git a/AmayoWeb/src/services/user.js b/AmayoWeb/src/services/user.js
new file mode 100644
index 0000000..cd5d701
--- /dev/null
+++ b/AmayoWeb/src/services/user.js
@@ -0,0 +1,80 @@
+import { secureAxios } from './auth'
+
+export const userService = {
+ // Get user dashboard data
+ async getUserData() {
+ try {
+ const response = await secureAxios.get('/api/user/me')
+ return response.data
+ } catch (error) {
+ console.error('Failed to get user data:', error)
+ throw error
+ }
+ },
+
+ // Redeem coupon
+ async redeemCoupon(code) {
+ try {
+ const response = await secureAxios.post('/api/user/coupon/redeem', { code })
+ return response.data
+ } catch (error) {
+ console.error('Failed to redeem coupon:', error)
+ throw error
+ }
+ },
+
+ // Get user playlists
+ async getUserPlaylists() {
+ try {
+ const response = await secureAxios.get('/api/user/playlists')
+ return response.data
+ } catch (error) {
+ console.error('Failed to get user playlists:', error)
+ throw error
+ }
+ },
+
+ // Create playlist
+ async createPlaylist(data) {
+ try {
+ const response = await secureAxios.post('/api/user/playlists', data)
+ return response.data
+ } catch (error) {
+ console.error('Failed to create playlist:', error)
+ throw error
+ }
+ },
+
+ // Get playlist details
+ async getPlaylistDetails(id) {
+ try {
+ const response = await secureAxios.get(`/api/user/playlists/${id}`)
+ return response.data
+ } catch (error) {
+ console.error('Failed to get playlist details:', error)
+ throw error
+ }
+ },
+
+ // Reorder playlist
+ async reorderPlaylist(id, tracks) {
+ try {
+ const response = await secureAxios.put(`/api/user/playlists/${id}/reorder`, { tracks })
+ return response.data
+ } catch (error) {
+ console.error('Failed to reorder playlist:', error)
+ throw error
+ }
+ },
+
+ // Delete playlist
+ async deletePlaylist(id) {
+ try {
+ const response = await secureAxios.delete(`/api/user/playlists/${id}`)
+ return response.data
+ } catch (error) {
+ console.error('Failed to delete playlist:', error)
+ throw error
+ }
+ }
+}
diff --git a/AmayoWeb/src/views/AuthCallback.vue b/AmayoWeb/src/views/AuthCallback.vue
index 215d1de..b6c49eb 100644
--- a/AmayoWeb/src/views/AuthCallback.vue
+++ b/AmayoWeb/src/views/AuthCallback.vue
@@ -16,28 +16,22 @@ const router = useRouter()
const message = ref('Autenticando con Discord...')
onMounted(async () => {
- const urlParams = new URLSearchParams(window.location.search)
- const code = urlParams.get('code')
- const error = urlParams.get('error')
-
- if (error) {
- message.value = 'Error en la autenticación'
- setTimeout(() => router.push('/'), 2000)
- return
- }
-
- if (code) {
- try {
- await authService.handleCallback(code)
+ try {
+ // El backend ya procesó OAuth y estableció la cookie de sesión
+ // Solo necesitamos obtener los datos del usuario
+ const user = await authService.getCurrentUser()
+
+ if (user) {
message.value = '¡Autenticación exitosa!'
- setTimeout(() => router.push('/dashboard'), 1500)
- } catch (err) {
- message.value = 'Error al procesar la autenticación'
- console.error(err)
+ // Redirigir al selector de servidors
+ setTimeout(() => router.push('/dash/me'), 500)
+ } else {
+ message.value = 'Error: No se pudo obtener la sesión'
setTimeout(() => router.push('/'), 2000)
}
- } else {
- message.value = 'Código no encontrado'
+ } catch (error) {
+ console.error('Auth callback error:', error)
+ message.value = 'Error al procesar la autenticación'
setTimeout(() => router.push('/'), 2000)
}
})
diff --git a/AmayoWeb/src/views/DashboardView.vue b/AmayoWeb/src/views/DashboardView.vue
new file mode 100644
index 0000000..9259d9c
--- /dev/null
+++ b/AmayoWeb/src/views/DashboardView.vue
@@ -0,0 +1,3401 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Amayo Dashboard
+
Manage your server with the power of the void.
+
New Event
+
+
+
+
+
+
+
+
+
+
Recent server activity based on audit logs from the last 7 days
+
+
+
+
+
+
+
+ {{ memberCount.toLocaleString() }}
+ {{ onlinePercentage }}% online
+ {{ onlineCount }} online
+ Loading...
+
+
+
+
+
+
+ {{ boostCount }}
+ Level {{ boostTier }}
+
+
+
+
+
+
+
+ {{ action.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading your data...
+
+
+
+
+
+
+
Your Personal Space
+
Manage your subscription, levels, and music library.
+
+
+
+
+
+
+
+
+
+
+
Valid until {{ vipExpiryDate }}
+
Permanent Benefits Active
+
Unlock the full potential of Amayo with VIP.
+
+
+
+
🎵
+
+ {{ userData?.subscription?.maxVolume || 100 }}%
+ Volume Limit
+
+
+
+
📥
+
+ {{ userData?.subscription?.importLimit || 100 }}
+ Playlist Import
+
+
+
+
✨
+
+ {{ userData?.subscription?.recommendationLevel === 'pro' ? 'Pro' : 'Std' }}
+ AI Suggestions
+
+
+
+
🚀
+
+ {{ isVip ? 'Yes' : 'No' }}
+ Early Access
+
+
+
+
🎨
+
+ {{ isVip ? 'Custom' : 'Default' }}
+ Profile Theme
+
+
+
+
Get Premium
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ userData?.xp || 0 }}
+ / {{ userData?.nextLevelXp || 100 }} XP
+
+
+
Chat more to earn XP!
+
+
+
+
+
+
+
+
Got a promo code? Enter it below to claim rewards.
+
+
+
+ {{ redeemingCoupon ? '...' : 'Claim' }}
+
+
+
{{ couponMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Personal Use
+
+
Get
+
+
+
+
+
+
+
+
+
Personal Use
+
+
Get
+
+
+ ✓
+ Better Recommendations
+
+
+ ✓
+ Higher song import limit 100-200
+
+
+ ✓
+ 1 Boost Rewards
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Current Plan
+ {{ userData?.subscription?.recommendationLevel === 'pro' ? 'Premium' : 'Basic' }}
+
+
+ Status
+ Active
+
+
+ Next Billing
+ {{ vipExpiryDate }}
+
+
+
+
+
Your Active Benefits
+
+
✨ {{ userData.subscription.maxVolume }}% Volume
+
📥 {{ userData.subscription.importLimit }} Song Import
+
🤖 Pro AI Recommendations
+
+ No active benefits - redeem a code to get started!
+
+
+
+
+
+ Manage Subscription
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create New Playlist
+
+
+
+
+ 🎵
+
+
+
{{ playlist.name }}
+
{{ playlist._count?.tracks || 0 }} tracks
+
+
+
+
+
+
+
+
+
Create New Playlist
+
+ Name
+
+
+
+ Description
+
+
+
+
+ Cancel
+
+ {{ creatingPlaylist ? 'Creating...' : 'Create' }}
+
+
+
+
+
+
+
+
+
+
+
Loading tracks...
+
+
+
+
No tracks yet. Add some music!
+
+
+
+
+
⋮⋮
+
+ {{ track.title }}
+ {{ track.author }}
+
+
{{ Math.floor(track.duration / 60000) }}:{{ ((track.duration % 60000) / 1000).toFixed(0).padStart(2, '0') }}
+
+
+
+
+
+ {{ isReordering ? 'Saving...' : 'Save Order' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ guildSettings.prefix }}
+
+
+
+
+
+
+ {{ savingSettings ? 'Saving...' : 'Save' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ getRoleName(roleId) }}
+
+
+
No staff roles configured
+
+
+
+
+
+
+
Select a role...
+
+
+
{{ getRoleName(selectedRoleToAdd) }}
+
+
▼
+
+
+
+
+ Add Role
+
+
+
+
+
+
+
{{ getRoleName(roleId) }}
+
×
+
+
+
+
+
+ {{ savingSettings ? 'Saving...' : 'Save' }}
+
+
+
+
+
+
+
+
+
+
+
{{ guildSettings.aiRolePrompt }}
+
No AI role prompt configured
+
+
+
+
+
{{ tempAIPrompt?.length || 0 }} / 1500
+
+
+ {{ savingSettings ? 'Saving...' : 'Save' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmayoWeb/src/views/DocsView.vue b/AmayoWeb/src/views/DocsView.vue
deleted file mode 100644
index 30fe26e..0000000
--- a/AmayoWeb/src/views/DocsView.vue
+++ /dev/null
@@ -1,466 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('docs.introduction') }}
- {{ t('docs.introText') }}
-
-
-
-
• {{ t('docs.inviteBot') }}
-
-
-
• {{ t('docs.joinSupport') }}
-
-
-
• {{ t('docs.privacyPolicy') }}
-
-
-
• {{ t('docs.termsOfService') }}
-
-
-
-
-
💡
-
- {{ t('docs.defaultPrefix') }}: {{ t('docs.prefixInfo') }}
-
-
-
-
-
-
-
- {{ t('docs.dropsDescription') }}
-
-
-
-
- {{ t('docs.utilitiesDescription') }}
-
-
-
-
- {{ t('docs.economyDescription') }}
-
-
-
-
- {{ t('docs.moderationDescription') }}
-
-
-
-
-
-
-
-
-
-
diff --git a/AmayoWeb/src/views/EmbedsView.vue b/AmayoWeb/src/views/EmbedsView.vue
new file mode 100644
index 0000000..1539523
--- /dev/null
+++ b/AmayoWeb/src/views/EmbedsView.vue
@@ -0,0 +1,373 @@
+
+
+
+
+
+
+
+
+
Loading your embeds...
+
+
+
+
+
🎨
+
No embeds yet
+
Create your first custom embed to make your server stand out!
+
+ Start Creating
+
+
+
+
+
+
+
+
+
+
+ 🧩
+ {{ embed.components.length }} components
+
+
+ Updated {{ formatDate(embed.updatedAt) }}
+
+
+
+
+
+
+ ✏️
+
+
+ 🗑️
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmayoWeb/src/views/HomeView.vue b/AmayoWeb/src/views/HomeView.vue
index 1161c07..010ff6c 100644
--- a/AmayoWeb/src/views/HomeView.vue
+++ b/AmayoWeb/src/views/HomeView.vue
@@ -3,6 +3,7 @@
+
@@ -10,6 +11,7 @@
import AnimatedBackground from '../components/AnimatedBackground.vue'
import IslandNavbar from '../components/IslandNavbar.vue'
import HeroSection from '../components/HeroSection.vue'
+import FeaturesSection from '../components/FeaturesSection.vue'
diff --git a/AmayoWeb/src/views/PremiumView.vue b/AmayoWeb/src/views/PremiumView.vue
new file mode 100644
index 0000000..9c92c20
--- /dev/null
+++ b/AmayoWeb/src/views/PremiumView.vue
@@ -0,0 +1,335 @@
+