-
+
+
@@ -906,6 +1499,10 @@
case 'quests': await loadQuests(); break;
case 'shop': await loadShop(); break;
case 'inventory': await loadInventory(); break;
+ case 'admin-xp': await loadXpConfig(); break;
+ case 'admin-quests': await loadAdminQuests(); break;
+ case 'admin-achievements': await loadAdminAchievements(); break;
+ case 'admin-shop': await loadAdminShop(); break;
}
}
@@ -1122,6 +1719,490 @@
}
}
+ async function loadXpConfig() {
+ if (!currentGuild) return;
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/config');
+ if (!res.ok) {
+ if (res.status === 403) {
+ showSaveStatus('xpSaveStatus', 'You do not have admin access to this server', 'error');
+ }
+ return;
+ }
+
+ const data = await res.json();
+ const config = data.xpConfig || {};
+
+ document.getElementById('xpMsgMin').value = config.message_xp_min ?? 15;
+ document.getElementById('xpMsgMax').value = config.message_xp_max ?? 25;
+ document.getElementById('xpMsgCooldown').value = config.message_cooldown ?? 60;
+ document.getElementById('xpReaction').value = config.reaction_xp ?? 5;
+ document.getElementById('xpVoice').value = config.voice_xp_per_minute ?? 2;
+ document.getElementById('xpDaily').value = config.daily_xp ?? 100;
+ document.getElementById('xpBase').value = config.xp_base ?? 100;
+ document.getElementById('levelUpChannel').value = config.levelup_channel_id || '';
+ document.getElementById('levelUpEnabled').checked = config.levelup_enabled !== false;
+ document.getElementById('levelUpDm').checked = config.levelup_dm === true;
+ document.getElementById('xpMultiplier').value = config.xp_multiplier ?? 1.0;
+ document.getElementById('weekendMultiplier').value = config.weekend_multiplier ?? 1.5;
+
+ } catch (e) {
+ console.error('Failed to load XP config:', e);
+ }
+ }
+
+ async function saveXpConfig(e) {
+ e.preventDefault();
+
+ if (!currentGuild) return;
+
+ const form = document.getElementById('xpSettingsForm');
+ const data = {
+ message_xp_min: parseInt(form.xpMsgMin.value) || 15,
+ message_xp_max: parseInt(form.xpMsgMax.value) || 25,
+ message_cooldown: parseInt(form.xpMsgCooldown.value) || 60,
+ reaction_xp: parseInt(form.xpReaction.value) || 5,
+ voice_xp_per_minute: parseInt(form.xpVoice.value) || 2,
+ daily_xp: parseInt(form.xpDaily.value) || 100,
+ xp_base: parseInt(form.xpBase.value) || 100,
+ levelup_channel_id: form.levelUpChannel.value || null,
+ levelup_enabled: form.levelUpEnabled.checked,
+ levelup_dm: form.levelUpDm.checked,
+ xp_multiplier: parseFloat(form.xpMultiplier.value) || 1.0,
+ weekend_multiplier: parseFloat(form.weekendMultiplier.value) || 1.5
+ };
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/xp-config', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+
+ if (res.ok) {
+ showSaveStatus('xpSaveStatus', 'Settings saved successfully!', 'success');
+ } else {
+ const err = await res.json();
+ showSaveStatus('xpSaveStatus', err.error || 'Failed to save settings', 'error');
+ }
+ } catch (e) {
+ showSaveStatus('xpSaveStatus', 'Failed to save settings', 'error');
+ }
+ }
+
+ function showSaveStatus(elementId, message, type) {
+ const el = document.getElementById(elementId);
+ el.textContent = message;
+ el.className = 'save-status ' + type;
+ el.classList.remove('hidden');
+
+ setTimeout(() => {
+ el.classList.add('hidden');
+ }, 5000);
+ }
+
+ const QUEST_TYPES = { daily: { emoji: '☀️', name: 'Daily' }, weekly: { emoji: '📅', name: 'Weekly' }, special: { emoji: '⭐', name: 'Special' } };
+ const OBJECTIVES = { messages: '💬', reactions: '😄', voice_minutes: '🎙️', commands: '⚡', daily_claims: '🎁', level_ups: '📈', xp_earned: '✨' };
+
+ async function loadAdminQuests() {
+ if (!currentGuild) return;
+
+ const container = document.getElementById('adminQuestList');
+ container.innerHTML = '
';
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/quests');
+ if (!res.ok) {
+ container.innerHTML = '
';
+ return;
+ }
+
+ const data = await res.json();
+
+ if (!data.quests || data.quests.length === 0) {
+ container.innerHTML = '
🎯
No quests configured yet
';
+ return;
+ }
+
+ container.innerHTML = data.quests.map(q => `
+
+
${QUEST_TYPES[q.quest_type]?.emoji || '🎯'}
+
+
${q.name} ${!q.active ? '(Inactive)' : ''}
+
${OBJECTIVES[q.objective] || ''} Target: ${q.target_value} | Reward: ${q.xp_reward} XP
+
+
+
+
+
+
+ `).join('');
+
+ window.adminQuests = data.quests;
+
+ } catch (e) {
+ container.innerHTML = '
';
+ }
+ }
+
+ function openQuestModal(quest = null) {
+ document.getElementById('questModalTitle').textContent = quest ? 'Edit Quest' : 'Create Quest';
+ document.getElementById('questEditId').value = quest?.id || '';
+ document.getElementById('questName').value = quest?.name || '';
+ document.getElementById('questDescription').value = quest?.description || '';
+ document.getElementById('questType').value = quest?.quest_type || 'daily';
+ document.getElementById('questObjective').value = quest?.objective || 'messages';
+ document.getElementById('questTarget').value = quest?.target_value || 10;
+ document.getElementById('questXpReward').value = quest?.xp_reward || 100;
+ document.getElementById('questDuration').value = quest?.duration_hours || 24;
+ document.getElementById('questRepeatable').checked = quest?.repeatable || false;
+ document.getElementById('questActive').checked = quest?.active !== false;
+ document.getElementById('questModal').classList.remove('hidden');
+ }
+
+ function closeQuestModal() {
+ document.getElementById('questModal').classList.add('hidden');
+ }
+
+ function editQuest(questId) {
+ const quest = window.adminQuests?.find(q => q.id === questId);
+ if (quest) openQuestModal(quest);
+ }
+
+ async function saveQuest(e) {
+ e.preventDefault();
+
+ if (!currentGuild) return;
+
+ const questId = document.getElementById('questEditId').value;
+ const data = {
+ name: document.getElementById('questName').value,
+ description: document.getElementById('questDescription').value,
+ quest_type: document.getElementById('questType').value,
+ objective: document.getElementById('questObjective').value,
+ target_value: parseInt(document.getElementById('questTarget').value),
+ xp_reward: parseInt(document.getElementById('questXpReward').value),
+ duration_hours: parseInt(document.getElementById('questDuration').value) || 0,
+ repeatable: document.getElementById('questRepeatable').checked,
+ active: document.getElementById('questActive').checked
+ };
+
+ try {
+ const url = questId
+ ? '/api/guild/' + currentGuild + '/quests/' + questId
+ : '/api/guild/' + currentGuild + '/quests';
+
+ const res = await fetch(url, {
+ method: questId ? 'PUT' : 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+
+ if (res.ok) {
+ closeQuestModal();
+ showSaveStatus('questSaveStatus', questId ? 'Quest updated!' : 'Quest created!', 'success');
+ await loadAdminQuests();
+ } else {
+ const err = await res.json();
+ showSaveStatus('questSaveStatus', err.error || 'Failed to save quest', 'error');
+ }
+ } catch (e) {
+ showSaveStatus('questSaveStatus', 'Failed to save quest', 'error');
+ }
+ }
+
+ async function deleteQuest(questId) {
+ if (!confirm('Are you sure you want to delete this quest?')) return;
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/quests/' + questId, {
+ method: 'DELETE'
+ });
+
+ if (res.ok) {
+ showSaveStatus('questSaveStatus', 'Quest deleted', 'success');
+ await loadAdminQuests();
+ } else {
+ showSaveStatus('questSaveStatus', 'Failed to delete quest', 'error');
+ }
+ } catch (e) {
+ showSaveStatus('questSaveStatus', 'Failed to delete quest', 'error');
+ }
+ }
+
+ const TRIGGER_TYPES = {
+ level: { emoji: '📈', name: 'Reach Level' },
+ prestige: { emoji: '👑', name: 'Reach Prestige' },
+ total_xp: { emoji: '✨', name: 'Total XP Earned' },
+ messages: { emoji: '💬', name: 'Messages Sent' },
+ reactions_given: { emoji: '😄', name: 'Reactions Given' },
+ reactions_received: { emoji: '❤️', name: 'Reactions Received' },
+ voice_minutes: { emoji: '🎙️', name: 'Voice Minutes' },
+ daily_streak: { emoji: '🔥', name: 'Daily Streak' },
+ commands_used: { emoji: '⚡', name: 'Commands Used' }
+ };
+
+ const ITEM_TYPES = {
+ badge: { emoji: '🏅', name: 'Badge' },
+ title: { emoji: '🏷️', name: 'Title' },
+ background: { emoji: '🎨', name: 'Background' },
+ booster: { emoji: '⚡', name: 'XP Booster' },
+ role: { emoji: '👑', name: 'Role' },
+ special: { emoji: '✨', name: 'Special' }
+ };
+
+ async function loadAdminAchievements() {
+ if (!currentGuild) return;
+
+ const container = document.getElementById('adminAchievementList');
+ container.innerHTML = '
';
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/achievements');
+ if (!res.ok) {
+ container.innerHTML = '
Failed to load achievements
';
+ return;
+ }
+
+ const data = await res.json();
+
+ if (!data.achievements || data.achievements.length === 0) {
+ container.innerHTML = '
🏅
No achievements configured yet
';
+ return;
+ }
+
+ container.innerHTML = data.achievements.map(a => `
+
+
${a.icon || '🏆'}
+
+
${a.name} ${a.hidden ? '(Hidden)' : ''}
+
${TRIGGER_TYPES[a.trigger_type]?.name || a.trigger_type}: ${a.trigger_value} | Reward: ${a.reward_xp || 0} XP${a.reward_role_id ? ' + Role' : ''}
+
+
+
+
+
+
+ `).join('');
+
+ window.adminAchievements = data.achievements;
+
+ } catch (e) {
+ container.innerHTML = '
Failed to load achievements
';
+ }
+ }
+
+ function openAchievementModal(achievement = null) {
+ document.getElementById('achievementModalTitle').textContent = achievement ? 'Edit Achievement' : 'Create Achievement';
+ document.getElementById('achievementEditId').value = achievement?.id || '';
+ document.getElementById('achievementName').value = achievement?.name || '';
+ document.getElementById('achievementIcon').value = achievement?.icon || '🏆';
+ document.getElementById('achievementDescription').value = achievement?.description || '';
+ document.getElementById('achievementTrigger').value = achievement?.trigger_type || 'level';
+ document.getElementById('achievementValue').value = achievement?.trigger_value || 10;
+ document.getElementById('achievementXpReward').value = achievement?.reward_xp || 100;
+ document.getElementById('achievementRoleReward').value = achievement?.reward_role_id || '';
+ document.getElementById('achievementHidden').checked = achievement?.hidden || false;
+ document.getElementById('achievementModal').classList.remove('hidden');
+ }
+
+ function closeAchievementModal() {
+ document.getElementById('achievementModal').classList.add('hidden');
+ }
+
+ function editAchievement(achievementId) {
+ const achievement = window.adminAchievements?.find(a => a.id === achievementId);
+ if (achievement) openAchievementModal(achievement);
+ }
+
+ async function saveAchievement(e) {
+ e.preventDefault();
+
+ if (!currentGuild) return;
+
+ const achievementId = document.getElementById('achievementEditId').value;
+ const data = {
+ name: document.getElementById('achievementName').value,
+ icon: document.getElementById('achievementIcon').value || '🏆',
+ description: document.getElementById('achievementDescription').value,
+ trigger_type: document.getElementById('achievementTrigger').value,
+ trigger_value: parseInt(document.getElementById('achievementValue').value),
+ reward_xp: parseInt(document.getElementById('achievementXpReward').value) || 0,
+ reward_role_id: document.getElementById('achievementRoleReward').value || null,
+ hidden: document.getElementById('achievementHidden').checked
+ };
+
+ try {
+ const url = achievementId
+ ? '/api/guild/' + currentGuild + '/achievements/' + achievementId
+ : '/api/guild/' + currentGuild + '/achievements';
+
+ const res = await fetch(url, {
+ method: achievementId ? 'PUT' : 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+
+ if (res.ok) {
+ closeAchievementModal();
+ showSaveStatus('achievementSaveStatus', achievementId ? 'Achievement updated!' : 'Achievement created!', 'success');
+ await loadAdminAchievements();
+ } else {
+ const err = await res.json();
+ showSaveStatus('achievementSaveStatus', err.error || 'Failed to save achievement', 'error');
+ }
+ } catch (e) {
+ showSaveStatus('achievementSaveStatus', 'Failed to save achievement', 'error');
+ }
+ }
+
+ async function deleteAchievement(achievementId) {
+ if (!confirm('Are you sure you want to delete this achievement?')) return;
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/achievements/' + achievementId, {
+ method: 'DELETE'
+ });
+
+ if (res.ok) {
+ showSaveStatus('achievementSaveStatus', 'Achievement deleted', 'success');
+ await loadAdminAchievements();
+ } else {
+ showSaveStatus('achievementSaveStatus', 'Failed to delete achievement', 'error');
+ }
+ } catch (e) {
+ showSaveStatus('achievementSaveStatus', 'Failed to delete achievement', 'error');
+ }
+ }
+
+ async function loadAdminShop() {
+ if (!currentGuild) return;
+
+ const container = document.getElementById('adminShopList');
+ container.innerHTML = '
';
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/shop/admin');
+ if (!res.ok) {
+ container.innerHTML = '
Failed to load shop items
';
+ return;
+ }
+
+ const data = await res.json();
+
+ if (!data.items || data.items.length === 0) {
+ container.innerHTML = '
🏪
No shop items configured yet
';
+ return;
+ }
+
+ container.innerHTML = data.items.map(item => `
+
+
${ITEM_TYPES[item.item_type]?.emoji || '🎁'}
+
+
${item.name} ${!item.available ? '(Disabled)' : ''}
+
${item.price.toLocaleString()} XP | Stock: ${item.stock ?? 'Unlimited'}${item.level_required > 0 ? ' | Level ' + item.level_required : ''}
+
+
+
+
+
+
+ `).join('');
+
+ window.adminShopItems = data.items;
+
+ } catch (e) {
+ container.innerHTML = '
Failed to load shop items
';
+ }
+ }
+
+ function openShopModal(item = null) {
+ document.getElementById('shopModalTitle').textContent = item ? 'Edit Shop Item' : 'Add Shop Item';
+ document.getElementById('shopEditId').value = item?.id || '';
+ document.getElementById('shopItemName').value = item?.name || '';
+ document.getElementById('shopItemType').value = item?.item_type || 'badge';
+ document.getElementById('shopItemDescription').value = item?.description || '';
+ document.getElementById('shopItemPrice').value = item?.price || 100;
+ document.getElementById('shopItemStock').value = item?.stock ?? '';
+ document.getElementById('shopItemLevelReq').value = item?.level_required || 0;
+ document.getElementById('shopItemPrestigeReq').value = item?.prestige_required || 0;
+ document.getElementById('shopItemEnabled').checked = item?.available !== false;
+ document.getElementById('shopModal').classList.remove('hidden');
+ }
+
+ function closeShopModal() {
+ document.getElementById('shopModal').classList.add('hidden');
+ }
+
+ function editShopItem(itemId) {
+ const item = window.adminShopItems?.find(i => i.id === itemId);
+ if (item) openShopModal(item);
+ }
+
+ async function saveShopItem(e) {
+ e.preventDefault();
+
+ if (!currentGuild) return;
+
+ const itemId = document.getElementById('shopEditId').value;
+ const stockVal = document.getElementById('shopItemStock').value;
+ const data = {
+ name: document.getElementById('shopItemName').value,
+ item_type: document.getElementById('shopItemType').value,
+ description: document.getElementById('shopItemDescription').value,
+ price: parseInt(document.getElementById('shopItemPrice').value),
+ stock: stockVal === '' || stockVal === '-1' ? null : parseInt(stockVal),
+ level_required: parseInt(document.getElementById('shopItemLevelReq').value) || 0,
+ prestige_required: parseInt(document.getElementById('shopItemPrestigeReq').value) || 0,
+ available: document.getElementById('shopItemEnabled').checked
+ };
+
+ try {
+ const url = itemId
+ ? '/api/guild/' + currentGuild + '/shop/' + itemId
+ : '/api/guild/' + currentGuild + '/shop';
+
+ const res = await fetch(url, {
+ method: itemId ? 'PUT' : 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+
+ if (res.ok) {
+ closeShopModal();
+ showSaveStatus('shopSaveStatus', itemId ? 'Item updated!' : 'Item added!', 'success');
+ await loadAdminShop();
+ } else {
+ const err = await res.json();
+ showSaveStatus('shopSaveStatus', err.error || 'Failed to save item', 'error');
+ }
+ } catch (e) {
+ showSaveStatus('shopSaveStatus', 'Failed to save item', 'error');
+ }
+ }
+
+ async function deleteShopItem(itemId) {
+ if (!confirm('Are you sure you want to delete this shop item?')) return;
+
+ try {
+ const res = await fetch('/api/guild/' + currentGuild + '/shop/' + itemId, {
+ method: 'DELETE'
+ });
+
+ if (res.ok) {
+ showSaveStatus('shopSaveStatus', 'Item deleted', 'success');
+ await loadAdminShop();
+ } else {
+ showSaveStatus('shopSaveStatus', 'Failed to delete item', 'error');
+ }
+ } catch (e) {
+ showSaveStatus('shopSaveStatus', 'Failed to delete item', 'error');
+ }
+ }
+
+ document.getElementById('xpSettingsForm')?.addEventListener('submit', saveXpConfig);
+
init();