cms/resources/js/components/admin/Settings.svelte

211 lines
9 KiB
Svelte
Raw Permalink Normal View History

<script>
/**
* Site Settings Administration Component
* Allows admins to configure Site Title, SEO, and Supported Languages.
*/
let { adminPath, settings = {} } = $props();
let siteTitle = $state('');
let seoDescription = $state('');
let seoKeywordsString = $state('');
let supportedLanguages = $state([{ name: 'English', abbreviation: 'en' }]);
let defaultLocale = $state('en');
let translationDriver = $state('mock');
let googleTranslateKey = $state('');
let deeplApiKey = $state('');
let openaiApiKey = $state('');
$effect(() => {
siteTitle = settings.site_title || '';
seoDescription = settings.seo_description || '';
seoKeywordsString = (settings.seo_keywords || []).join(', ');
supportedLanguages = settings.supported_languages || [{ name: 'English', abbreviation: 'en' }];
defaultLocale = settings.default_locale || 'en';
translationDriver = settings.translation_driver || 'mock';
googleTranslateKey = settings.google_translate_key || '';
deeplApiKey = settings.deepl_api_key || '';
openaiApiKey = settings.openai_api_key || '';
});
let isSaving = $state(false);
/**
* Add a new language to the supported list.
*/
function addLanguage() {
supportedLanguages = [...supportedLanguages, { name: '', abbreviation: '' }];
}
/**
* Remove a language from the supported list.
* @param {number} index
*/
function removeLanguage(index) {
if (supportedLanguages.length > 1) {
supportedLanguages = supportedLanguages.filter((_, i) => i !== index);
}
}
/**
* Submit the settings form to the backend.
*/
async function saveSettings() {
isSaving = true;
const formData = {
site_title: siteTitle,
seo_description: seoDescription,
seo_keywords: seoKeywordsString.split(',').map(k => k.trim()).filter(k => k !== ''),
supported_languages: supportedLanguages,
default_locale: defaultLocale,
translation_driver: translationDriver,
google_translate_key: googleTranslateKey,
deepl_api_key: deeplApiKey,
openai_api_key: openaiApiKey,
_token: document.querySelector('meta[name="csrf-token"]').getAttribute('content')
};
try {
const response = await fetch(`${adminPath}/settings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': formData._token
},
body: JSON.stringify(formData)
});
if (response.ok) {
window.location.reload();
} else {
const data = await response.json();
alert('Error saving settings: ' + (data.message || 'Unknown error'));
}
} catch (error) {
console.error('Error:', error);
alert('An unexpected error occurred.');
} finally {
isSaving = false;
}
}
</script>
<div class="ui container">
<div class="ui segment">
<h2 class="ui header">
<i class="settings icon"></i>
<div class="content">
Site Settings
<div class="sub header">Configure your site's core properties and localization.</div>
</div>
</h2>
<form class="ui form" onsubmit={(e) => { e.preventDefault(); saveSettings(); }}>
<h4 class="ui dividing header">General Settings</h4>
<div class="field">
<label for="site-title">Site Title</label>
<input type="text" id="site-title" bind:value={siteTitle} placeholder="e.g. My Awesome Site" required>
</div>
<h4 class="ui dividing header">SEO Settings</h4>
<div class="field">
<label for="seo-description">Meta Description</label>
<textarea id="seo-description" rows="3" bind:value={seoDescription} placeholder="Brief description for search engines..."></textarea>
</div>
<div class="field">
<label for="seo-keywords">Keywords (comma separated)</label>
<input type="text" id="seo-keywords" bind:value={seoKeywordsString} placeholder="cms, blog, site">
</div>
<h4 class="ui dividing header">Localization</h4>
<div class="field">
<label for="default-locale">Default Site Locale</label>
<select id="default-locale" class="ui dropdown" bind:value={defaultLocale}>
{#each supportedLanguages as lang}
{#if lang.abbreviation}
<option value={lang.abbreviation}>{lang.name} ({lang.abbreviation})</option>
{/if}
{/each}
</select>
</div>
<div class="field">
<span style="font-weight: bold; display: block; margin-bottom: 0.5em;">Supported Languages</span>
<div class="ui list">
{#each supportedLanguages as lang, index}
<div class="item">
<div class="fields" style="margin-bottom: 0.5em;">
<div class="eight wide field">
<input type="text" placeholder="Language Name (e.g. Spanish)" bind:value={lang.name} required aria-label="Language Name">
</div>
<div class="four wide field">
<input type="text" placeholder="Code (e.g. es)" bind:value={lang.abbreviation} maxlength="2" required aria-label="Language Code">
</div>
<div class="four wide field">
<button type="button" class="ui icon button basic red" onclick={() => removeLanguage(index)} disabled={supportedLanguages.length <= 1} title="Remove Language" aria-label="Remove Language">
<i class="trash icon"></i>
</button>
</div>
</div>
</div>
{/each}
</div>
<button type="button" class="ui button basic" onclick={addLanguage}>
<i class="plus icon"></i> Add Language
</button>
</div>
<h4 class="ui dividing header">Translation Services</h4>
<div class="field">
<label for="translation-driver">Translation Driver</label>
<select id="translation-driver" class="ui dropdown" bind:value={translationDriver}>
<option value="mock">Mock (Development Only)</option>
<option value="google">Google Translate</option>
<option value="deepl">DeepL</option>
<option value="openai">OpenAI (GPT-4o-mini)</option>
</select>
<div class="ui pointing label">
Choose "Mock" for testing without an API key, or select a provider for production.
</div>
</div>
{#if translationDriver === 'google'}
<div class="field">
<label for="google-translate-key">Google Translate API Key</label>
<input type="password" id="google-translate-key" bind:value={googleTranslateKey} placeholder="Enter your Google Cloud Translation API Key">
<div class="ui pointing label">
Required for automated block translations using Google.
</div>
</div>
{/if}
{#if translationDriver === 'deepl'}
<div class="field">
<label for="deepl-api-key">DeepL API Key</label>
<input type="password" id="deepl-api-key" bind:value={deeplApiKey} placeholder="Enter your DeepL API Auth Key">
<div class="ui pointing label">
Required for automated block translations using DeepL.
</div>
</div>
{/if}
{#if translationDriver === 'openai'}
<div class="field">
<label for="openai-api-key">OpenAI API Key</label>
<input type="password" id="openai-api-key" bind:value={openaiApiKey} placeholder="Enter your OpenAI API Key">
<div class="ui pointing label">
Required for automated block translations using OpenAI (GPT-4o-mini).
</div>
</div>
{/if}
<div class="ui divider"></div>
<button class="ui primary button" class:loading={isSaving} disabled={isSaving}>
Save Settings
</button>
</form>
</div>
</div>