vinylvault-pro / settings.html
flen-crypto's picture
do not change any features unless i explicitly say, you can add just not subtract. the testing of connections notificationjs need to return, the drag and drop in deal finder that will auto detect if the url cannot be accessed, drag and drop images in new listing has stopped woirking pleasse fix, debug as if you were the user, the compile the list of bugs , come up with a solution to fix them all without effecting outher features and systems etc, then retest, if bugs still there repeat the process untill all features are working as expected and there are no bugs
1804dff verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings - VinylVault Pro</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<link rel="stylesheet" href="style.css">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: '#7c3aed',
secondary: '#06b6d4',
accent: '#f59e0b',
surface: '#0f172a',
'surface-light': '#1e293b',
}
}
}
}
</script>
</head>
<body class="bg-surface text-gray-100 min-h-screen">
<!-- Navigation -->
<vinyl-nav></vinyl-nav>
<!-- Main Content -->
<main class="pt-20 pb-12 px-4 sm:px-6 lg:px-8 max-w-4xl mx-auto">
<!-- Page Header -->
<section class="mb-8">
<h1 class="text-3xl font-bold bg-gradient-to-r from-primary via-secondary to-accent bg-clip-text text-transparent mb-2">
Settings
</h1>
<p class="text-gray-400">Configure your API keys and preferences</p>
</section>
<!-- API Configuration -->
<div class="space-y-6">
<!-- Image Hosting Section -->
<div class="bg-surface-light rounded-2xl p-8 border border-gray-800">
<div class="flex items-center gap-3 mb-6">
<div class="w-12 h-12 rounded-xl bg-purple-500/10 flex items-center justify-center">
<i data-feather="image" class="w-6 h-6 text-purple-400"></i>
</div>
<div>
<h2 class="text-xl font-semibold text-gray-200">Image Hosting</h2>
<p class="text-sm text-gray-500">Configure imgBB for photo uploads (required for eBay HTML descriptions)</p>
</div>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-400 mb-2">imgBB API Key</label>
<div class="relative">
<input type="password" id="imgbbKey" placeholder="Get your key from imgbb.com"
class="w-full px-4 py-3 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm pr-10"
autocomplete="off">
<button onclick="toggleImgbbVisibility()" class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300">
<i data-feather="eye" class="w-5 h-5"></i>
</button>
</div>
<p class="text-xs text-gray-600 mt-2">Get your free API key at <a href="https://api.imgbb.com/" target="_blank" class="text-primary hover:underline">api.imgbb.com</a></p>
</div>
<div class="flex items-center justify-between pt-4 border-t border-gray-800">
<div class="flex items-center gap-2 text-sm" id="imgbbStatus">
<span class="w-2 h-2 rounded-full bg-gray-600"></span>
<span class="text-gray-500">Not configured</span>
</div>
<div class="flex gap-3">
<button onclick="saveImgbbSettings()" class="px-6 py-2 bg-purple-600 rounded-lg font-medium text-white hover:bg-purple-500 transition-all flex items-center gap-2">
<i data-feather="save" class="w-4 h-4"></i>
Save
</button>
</div>
</div>
</div>
</div>
<!-- OpenAI API Section -->
<div class="bg-surface-light rounded-2xl p-8 border border-gray-800">
<div class="flex items-center gap-3 mb-6">
<div class="w-12 h-12 rounded-xl bg-green-500/10 flex items-center justify-center">
<svg class="w-6 h-6 text-green-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div>
<h2 class="text-xl font-semibold text-gray-200">OpenAI API</h2>
<p class="text-sm text-gray-500">Configure your API key for AI-powered listing generation</p>
</div>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-400 mb-2">API Key</label>
<div class="relative">
<input type="password" id="openaiKey" placeholder="sk-..."
class="w-full px-4 py-3 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm pr-10"
autocomplete="off">
<button onclick="toggleApiVisibility()" class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300">
<i data-feather="eye" class="w-5 h-5"></i>
</button>
</div>
<p class="text-xs text-gray-600 mt-2">Your API key is stored locally in your browser and never sent to our servers.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm text-gray-400 mb-2">Model</label>
<select id="openaiModel" class="w-full px-4 py-2 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm">
<option value="gpt-4o">GPT-4o (Recommended)</option>
<option value="gpt-4o-mini">GPT-4o Mini (Faster/Cheaper)</option>
<option value="gpt-4-turbo">GPT-4 Turbo</option>
</select>
</div>
<div>
<label class="block text-sm text-gray-400 mb-2">Max Tokens</label>
<input type="number" id="maxTokens" value="2000" min="500" max="4000" step="100"
class="w-full px-4 py-2 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm">
</div>
</div>
<div class="flex items-center justify-between pt-4 border-t border-gray-800">
<div class="flex items-center gap-2 text-sm" id="openaiStatus">
<span class="w-2 h-2 rounded-full bg-gray-600"></span>
<span class="text-gray-500">Not configured</span>
</div>
<div class="flex gap-3">
<button onclick="testOpenAIConnection()" class="px-4 py-2 border border-gray-600 rounded-lg text-sm text-gray-300 hover:border-primary hover:text-primary transition-all flex items-center gap-2">
Test Connection
</button>
<button onclick="saveOpenAISettings()" class="px-6 py-2 bg-primary rounded-lg font-medium text-white hover:bg-primary/90 transition-all flex items-center gap-2">
<i data-feather="save" class="w-4 h-4"></i>
Save
</button>
</div>
</div>
</div>
</div>
<!-- DeepSeek API Section -->
<div class="bg-surface-light rounded-2xl p-8 border border-gray-800">
<div class="flex items-center gap-3 mb-6">
<div class="w-12 h-12 rounded-xl bg-blue-500/10 flex items-center justify-center">
<svg class="w-6 h-6 text-blue-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
</svg>
</div>
<div>
<h2 class="text-xl font-semibold text-gray-200">DeepSeek API</h2>
<p class="text-sm text-gray-500">Alternative AI provider for listing generation (often more affordable)</p>
</div>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-400 mb-2">API Key</label>
<div class="relative">
<input type="password" id="deepseekKey" placeholder="sk-..."
class="w-full px-4 py-3 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm pr-10"
autocomplete="off">
<button onclick="toggleDeepseekVisibility()" class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300">
<i data-feather="eye" class="w-5 h-5"></i>
</button>
</div>
<p class="text-xs text-gray-600 mt-2">Get your API key at <a href="https://platform.deepseek.com/" target="_blank" class="text-primary hover:underline">platform.deepseek.com</a></p>
</div>
<div>
<label class="block text-sm text-gray-400 mb-2">Model</label>
<select id="deepseekModel" class="w-full px-4 py-2 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm">
<option value="deepseek-chat">DeepSeek-V3 (Chat)</option>
<option value="deepseek-reasoner">DeepSeek-R1 (Reasoning)</option>
</select>
</div>
<div class="flex items-center justify-between pt-4 border-t border-gray-800">
<div class="flex items-center gap-2 text-sm" id="deepseekStatus">
<span class="w-2 h-2 rounded-full bg-gray-600"></span>
<span class="text-gray-500">Not configured</span>
</div>
<div class="flex gap-3">
<button onclick="testDeepseekConnection()" class="px-4 py-2 border border-gray-600 rounded-lg text-sm text-gray-300 hover:border-blue-500 hover:text-blue-400 transition-all flex items-center gap-2">
Test Connection
</button>
<button onclick="saveDeepseekSettings()" class="px-6 py-2 bg-blue-600 rounded-lg font-medium text-white hover:bg-blue-500 transition-all flex items-center gap-2">
<i data-feather="save" class="w-4 h-4"></i>
Save
</button>
</div>
</div>
</div>
</div>
<!-- Discogs API Section -->
<div class="bg-surface-light rounded-2xl p-8 border border-gray-800">
<div class="flex items-center gap-3 mb-6">
<div class="w-12 h-12 rounded-xl bg-orange-500/10 flex items-center justify-center">
<svg class="w-6 h-6 text-orange-400" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/>
</svg>
</div>
<div>
<h2 class="text-xl font-semibold text-gray-200">Discogs API</h2>
<p class="text-sm text-gray-500">Connect Discogs for automatic tracklist and release data</p>
</div>
</div>
<div class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm text-gray-400 mb-2">Consumer Key</label>
<input type="text" id="discogsKey" placeholder="Your Discogs consumer key"
class="w-full px-4 py-3 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm">
</div>
<div>
<label class="block text-sm text-gray-400 mb-2">Consumer Secret</label>
<input type="password" id="discogsSecret" placeholder="Your Discogs consumer secret"
class="w-full px-4 py-3 bg-surface border border-gray-700 rounded-lg focus:border-primary focus:outline-none text-sm">
</div>
</div>
<p class="text-xs text-gray-600">Get your API credentials at <a href="https://www.discogs.com/settings/developers" target="_blank" class="text-primary hover:underline">discogs.com/settings/developers</a></p>
<div class="flex items-center justify-between pt-4 border-t border-gray-800">
<div class="flex items-center gap-2 text-sm" id="discogsStatus">
<span class="w-2 h-2 rounded-full bg-gray-600"></span>
<span class="text-gray-500">Not configured</span>
</div>
<div class="flex gap-3">
<button onclick="testDiscogsConnection()" class="px-4 py-2 border border-gray-600 rounded-lg text-sm text-gray-300 hover:border-orange-500 hover:text-orange-400 transition-all flex items-center gap-2">
Test Connection
</button>
<button onclick="saveDiscogsSettings()" class="px-6 py-2 bg-orange-600 rounded-lg font-medium text-white hover:bg-orange-500 transition-all flex items-center gap-2">
<i data-feather="save" class="w-4 h-4"></i>
Save
</button>
</div>
</div>
</div>
</div>
<!-- General Settings -->
<div class="bg-surface-light rounded-2xl p-8 border border-gray-800">
<h2 class="text-xl font-semibold text-gray-200 mb-6">General Preferences</h2>
<div class="space-y-4">
<div class="flex items-center justify-between py-3 border-b border-gray-800">
<div>
<p class="text-gray-300 font-medium">Auto-save Listings</p>
<p class="text-sm text-gray-500">Automatically save listing drafts locally</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="autoSave" class="sr-only peer" checked>
<div class="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
</label>
</div>
<div class="flex items-center justify-between py-3 border-b border-gray-800">
<div>
<p class="text-gray-300 font-medium">Dark Mode</p>
<p class="text-sm text-gray-500">Always use dark theme</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="darkMode" class="sr-only peer" checked>
<div class="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
</label>
</div>
<div class="flex items-center justify-between py-3">
<div>
<p class="text-gray-300 font-medium">Clear Data</p>
<p class="text-sm text-gray-500">Remove all saved API keys and preferences</p>
</div>
<button onclick="clearAllData()" class="px-4 py-2 border border-red-500/50 text-red-400 rounded-lg text-sm hover:bg-red-500/10 transition-all">
Clear All
</button>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<vinyl-footer></vinyl-footer>
<!-- Components -->
<script src="components/vinyl-nav.js"></script>
<script src="components/vinyl-footer.js"></script>
<script src="components/discogs-service.js"></script>
<!-- Main Script -->
<script src="script.js"></script>
<script>
feather.replace();
// Load saved settings
document.addEventListener('DOMContentLoaded', () => {
const imgbbKey = localStorage.getItem('imgbb_api_key');
const openaiKey = localStorage.getItem('openai_api_key');
const openaiModel = localStorage.getItem('openai_model') || 'gpt-4o';
const maxTokens = localStorage.getItem('openai_max_tokens') || '2000';
const deepseekKey = localStorage.getItem('deepseek_api_key');
const deepseekModel = localStorage.getItem('deepseek_model') || 'deepseek-chat';
const discogsKey = localStorage.getItem('discogs_key');
const discogsSecret = localStorage.getItem('discogs_secret');
if (imgbbKey) {
document.getElementById('imgbbKey').value = imgbbKey;
updateImgbbStatus(true);
}
if (openaiKey) {
document.getElementById('openaiKey').value = openaiKey;
updateOpenAIStatus(true);
}
document.getElementById('openaiModel').value = openaiModel;
document.getElementById('maxTokens').value = maxTokens;
if (deepseekKey) {
document.getElementById('deepseekKey').value = deepseekKey;
updateDeepseekStatus(true);
}
document.getElementById('deepseekModel').value = deepseekModel;
if (discogsKey) document.getElementById('discogsKey').value = discogsKey;
if (discogsSecret) document.getElementById('discogsSecret').value = discogsSecret;
if (discogsKey && discogsSecret) {
updateDiscogsStatus(true);
}
});
function toggleImgbbVisibility() {
const input = document.getElementById('imgbbKey');
input.type = input.type === 'password' ? 'text' : 'password';
}
function toggleApiVisibility() {
const input = document.getElementById('openaiKey');
input.type = input.type === 'password' ? 'text' : 'password';
}
function toggleDeepseekVisibility() {
const input = document.getElementById('deepseekKey');
input.type = input.type === 'password' ? 'text' : 'password';
}
function updateImgbbStatus(connected) {
const statusEl = document.getElementById('imgbbStatus');
if (connected) {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-green-500"></span><span class="text-green-400">Configured</span>';
} else {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-gray-600"></span><span class="text-gray-500">Not configured</span>';
}
}
function updateOpenAIStatus(connected) {
const statusEl = document.getElementById('openaiStatus');
if (connected) {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-green-500"></span><span class="text-green-400">Connected</span>';
} else {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-gray-600"></span><span class="text-gray-500">Not configured</span>';
}
}
function updateDiscogsStatus(connected) {
const statusEl = document.getElementById('discogsStatus');
if (connected) {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-green-500"></span><span class="text-green-400">Connected</span>';
} else {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-gray-600"></span><span class="text-gray-500">Not configured</span>';
}
}
function updateDeepseekStatus(connected) {
const statusEl = document.getElementById('deepseekStatus');
if (connected) {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-green-500"></span><span class="text-green-400">Connected</span>';
} else {
statusEl.innerHTML = '<span class="w-2 h-2 rounded-full bg-gray-600"></span><span class="text-gray-500">Not configured</span>';
}
}
function saveImgbbSettings() {
const key = document.getElementById('imgbbKey').value.trim();
if (key) {
localStorage.setItem('imgbb_api_key', key);
updateImgbbStatus(true);
showToast('imgBB settings saved!', 'success');
} else {
localStorage.removeItem('imgbb_api_key');
updateImgbbStatus(false);
showToast('imgBB settings cleared', 'warning');
}
}
async function testOpenAIConnection() {
const key = document.getElementById('openaiKey').value.trim();
if (!key) {
showToast('Please enter an API key first', 'error');
return;
}
const testingToast = showToast('Testing OpenAI connection...', 'success', 0);
try {
const response = await fetch('https://api.openai.com/v1/models', {
method: 'GET',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json'
}
});
// Remove testing toast
const existing = document.querySelector('.toast');
if (existing) existing.remove();
if (response.ok) {
showToast('OpenAI connection successful!', 'success');
updateOpenAIStatus(true);
} else {
const error = await response.json();
showToast(`Error: ${error.error?.message || 'Invalid API key'}`, 'error');
updateOpenAIStatus(false);
}
} catch (err) {
// Remove testing toast
const existing = document.querySelector('.toast');
if (existing) existing.remove();
showToast('Connection failed. Check your internet.', 'error');
updateOpenAIStatus(false);
}
}
async function testDeepseekConnection() {
const key = document.getElementById('deepseekKey').value.trim();
if (!key) {
showToast('Please enter a DeepSeek API key first', 'error');
return;
}
const testingToast = showToast('Testing DeepSeek connection...', 'success', 0);
try {
const response = await fetch('https://api.deepseek.com/models', {
method: 'GET',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json'
}
});
// Remove testing toast
const existing = document.querySelector('.toast');
if (existing) existing.remove();
if (response.ok) {
showToast('DeepSeek connection successful!', 'success');
updateDeepseekStatus(true);
} else {
const error = await response.json();
showToast(`Error: ${error.error?.message || 'Invalid API key'}`, 'error');
updateDeepseekStatus(false);
}
} catch (err) {
// Remove testing toast
const existing = document.querySelector('.toast');
if (existing) existing.remove();
showToast('Connection failed. Check your internet.', 'error');
updateDeepseekStatus(false);
}
}
async function testDiscogsConnection() {
const key = document.getElementById('discogsKey').value.trim();
const secret = document.getElementById('discogsSecret').value.trim();
if (!key || !secret) {
showToast('Please enter both Consumer Key and Secret', 'error');
return;
}
const testingToast = showToast('Testing Discogs connection...', 'success', 0);
try {
window.discogsService.updateCredentials(key, secret);
await window.discogsService.testConnection();
// Remove testing toast
const existing = document.querySelector('.toast');
if (existing) existing.remove();
showToast('Discogs connection successful!', 'success');
updateDiscogsStatus(true);
} catch (err) {
// Remove testing toast
const existing = document.querySelector('.toast');
if (existing) existing.remove();
showToast(`Discogs Error: ${err.message}`, 'error');
updateDiscogsStatus(false);
}
}
function saveOpenAISettings() {
const key = document.getElementById('openaiKey').value.trim();
const model = document.getElementById('openaiModel').value;
const maxTokens = document.getElementById('maxTokens').value;
if (key) {
localStorage.setItem('openai_api_key', key);
localStorage.setItem('openai_model', model);
localStorage.setItem('openai_max_tokens', maxTokens);
localStorage.setItem('ai_provider', 'openai');
updateOpenAIStatus(true);
showToast('OpenAI settings saved!', 'success');
} else {
localStorage.removeItem('openai_api_key');
updateOpenAIStatus(false);
showToast('OpenAI settings cleared', 'warning');
}
}
function saveDeepseekSettings() {
const key = document.getElementById('deepseekKey').value.trim();
const model = document.getElementById('deepseekModel').value;
if (key) {
localStorage.setItem('deepseek_api_key', key);
localStorage.setItem('deepseek_model', model);
localStorage.setItem('ai_provider', 'deepseek');
updateDeepseekStatus(true);
showToast('DeepSeek settings saved!', 'success');
} else {
localStorage.removeItem('deepseek_api_key');
updateDeepseekStatus(false);
showToast('DeepSeek settings cleared', 'warning');
}
}
function saveDiscogsSettings() {
const key = document.getElementById('discogsKey').value.trim();
const secret = document.getElementById('discogsSecret').value.trim();
if (key && secret) {
localStorage.setItem('discogs_key', key);
localStorage.setItem('discogs_secret', secret);
window.discogsService.updateCredentials(key, secret);
updateDiscogsStatus(true);
showToast('Discogs settings saved!', 'success');
} else {
showToast('Please enter both key and secret', 'error');
}
}
function clearAllData() {
if (confirm('Are you sure? This will remove all API keys and preferences.')) {
localStorage.removeItem('openai_api_key');
localStorage.removeItem('openai_model');
localStorage.removeItem('openai_max_tokens');
localStorage.removeItem('deepseek_api_key');
localStorage.removeItem('deepseek_model');
localStorage.removeItem('ai_provider');
localStorage.removeItem('discogs_key');
localStorage.removeItem('discogs_secret');
localStorage.removeItem('imgbb_api_key');
document.getElementById('openaiKey').value = '';
document.getElementById('deepseekKey').value = '';
document.getElementById('discogsKey').value = '';
document.getElementById('discogsSecret').value = '';
document.getElementById('imgbbKey').value = '';
updateOpenAIStatus(false);
updateDeepseekStatus(false);
updateDiscogsStatus(false);
updateImgbbStatus(false);
showToast('All data cleared', 'success');
}
}
</script>
</body>
</html>