184 lines
5.9 KiB
React
184 lines
5.9 KiB
React
import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
export default function Settings({ onClose }) {
|
|
const [githubToken, setGithubToken] = useState('');
|
|
const [giteaToken, setGiteaToken] = useState('');
|
|
const [giteaURL, setGiteaURL] = useState('');
|
|
const [avatarB64, setAvatarB64] = useState(null);
|
|
const [savedOk, setSavedOk] = useState(false);
|
|
const [avatarUploading, setAvatarUploading] = useState(false);
|
|
const fileInputRef = useRef(null);
|
|
|
|
function normalizeAndValidateGiteaUrl(rawUrl) {
|
|
const value = (rawUrl || '').trim();
|
|
if (!value) return { ok: true, value: '' };
|
|
|
|
let parsed;
|
|
try {
|
|
parsed = new URL(value);
|
|
} catch (_) {
|
|
return {
|
|
ok: false,
|
|
error: 'Ungültige Gitea-URL. Beispiel für IPv6: http://[2001:db8::1]:3000',
|
|
};
|
|
}
|
|
|
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
return {
|
|
ok: false,
|
|
error: 'Die Gitea-URL muss mit http:// oder https:// beginnen.',
|
|
};
|
|
}
|
|
|
|
return { ok: true, value: value.replace(/\/$/, '') };
|
|
}
|
|
|
|
useEffect(() => {
|
|
window.electronAPI.loadCredentials().then(data => {
|
|
if (data) {
|
|
setGithubToken(data.githubToken || '');
|
|
setGiteaToken(data.giteaToken || '');
|
|
setGiteaURL(data.giteaURL || '');
|
|
setAvatarB64(data.avatarB64 || null);
|
|
}
|
|
});
|
|
}, []);
|
|
|
|
function handleAvatarFileChange(e) {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
const reader = new FileReader();
|
|
reader.onload = (ev) => setAvatarB64(ev.target.result);
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
async function save() {
|
|
const checkedUrl = normalizeAndValidateGiteaUrl(giteaURL);
|
|
if (!checkedUrl.ok) {
|
|
alert(checkedUrl.error);
|
|
return;
|
|
}
|
|
window.electronAPI.saveCredentials({
|
|
githubToken,
|
|
giteaToken,
|
|
giteaURL: checkedUrl.value,
|
|
avatarB64: avatarB64 || null
|
|
});
|
|
|
|
// Avatar automatisch zu Gitea pushen, wenn Token + URL vorhanden
|
|
if (avatarB64 && giteaToken && checkedUrl.value) {
|
|
setAvatarUploading(true);
|
|
const result = await window.electronAPI.updateGiteaAvatar({
|
|
token: giteaToken,
|
|
url: checkedUrl.value,
|
|
imageBase64: avatarB64
|
|
});
|
|
setAvatarUploading(false);
|
|
if (!result.ok) {
|
|
console.warn('Avatar-Upload fehlgeschlagen:', result.error);
|
|
}
|
|
}
|
|
|
|
setSavedOk(true);
|
|
setTimeout(() => setSavedOk(false), 2500);
|
|
}
|
|
|
|
return (
|
|
<div className="modalContent card settings-modal-content">
|
|
|
|
<div className="settings-header">
|
|
<div className="settings-header-inner">
|
|
<div className="settings-avatar-wrap" onClick={() => fileInputRef.current?.click()} title="Profilbild ändern">
|
|
{avatarB64
|
|
? <img src={avatarB64} alt="Avatar" className="settings-avatar-img" />
|
|
: <div className="settings-avatar-placeholder">👤</div>
|
|
}
|
|
<div className="settings-avatar-overlay">✏️</div>
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
style={{ display: 'none' }}
|
|
onChange={handleAvatarFileChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<div className="settings-eyebrow">Konfiguration</div>
|
|
<h2>⚙️ Einstellungen</h2>
|
|
<p className="settings-subtitle">
|
|
Zugangsdaten für GitHub und Gitea hinterlegen.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-layout">
|
|
<div className="settings-column settings-column--left">
|
|
<section className="settings-panel settings-panel--credentials">
|
|
<div className="settings-panel-header">
|
|
<div>
|
|
<h3>Zugangsdaten</h3>
|
|
<p>API-Zugriffe für GitHub und Gitea konfigurieren.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-fields-grid">
|
|
<div className="input-group">
|
|
<label htmlFor="react-githubToken">GitHub Token</label>
|
|
<input
|
|
id="react-githubToken"
|
|
type="password"
|
|
placeholder="ghp_…"
|
|
value={githubToken}
|
|
onChange={e => setGithubToken(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="input-group">
|
|
<label htmlFor="react-giteaToken">Gitea Token</label>
|
|
<input
|
|
id="react-giteaToken"
|
|
type="password"
|
|
placeholder="Token hier einfügen"
|
|
value={giteaToken}
|
|
onChange={e => setGiteaToken(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="input-group input-group--wide">
|
|
<label htmlFor="react-giteaURL">Gitea URL</label>
|
|
<input
|
|
id="react-giteaURL"
|
|
type="text"
|
|
placeholder="https://gitea.example.com"
|
|
value={giteaURL}
|
|
onChange={e => setGiteaURL(e.target.value)}
|
|
/>
|
|
<div className="settings-connection-tools">
|
|
<div className="settings-inline-hint">
|
|
Hinweis: IPv6 mit Klammern eingeben, z.B. http://[2001:db8::1]:3000
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="modal-buttons settings-modal-actions">
|
|
<button
|
|
className="accent-btn"
|
|
onClick={save}
|
|
disabled={avatarUploading}
|
|
style={savedOk ? { background: 'var(--success)', borderColor: 'var(--success)' } : {}}
|
|
>
|
|
{avatarUploading ? '⏳ Bild wird hochgeladen…' : savedOk ? '✅ Gespeichert' : 'Speichern'}
|
|
</button>
|
|
{onClose && (
|
|
<button className="secondary" onClick={onClose}>Abbrechen</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|