version 0.0.24
This commit is contained in:
@@ -2,51 +2,6 @@ import { useState, useEffect } from 'react';
|
||||
import { api } from '../utils/api.js';
|
||||
import { useToast } from '../contexts/ToastContext.jsx';
|
||||
|
||||
function IconUploadRow({ label, settingKey, currentUrl, onUploaded, defaultSvg }) {
|
||||
const toast = useToast();
|
||||
|
||||
const handleUpload = async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
if (file.size > 1024 * 1024) return toast(`${label} icon must be less than 1MB`, 'error');
|
||||
try {
|
||||
let result;
|
||||
if (settingKey === 'icon_newchat') result = await api.uploadIconNewChat(file);
|
||||
else result = await api.uploadIconGroupInfo(file);
|
||||
onUploaded(settingKey, result.iconUrl);
|
||||
toast(`${label} icon updated`, 'success');
|
||||
} catch (e) {
|
||||
toast(e.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16 }}>
|
||||
<div style={{
|
||||
width: 48, height: 48, borderRadius: 10, background: 'var(--background)',
|
||||
border: '1px solid var(--border)', overflow: 'hidden', display: 'flex',
|
||||
alignItems: 'center', justifyContent: 'center', flexShrink: 0
|
||||
}}>
|
||||
{currentUrl ? (
|
||||
<img src={currentUrl} alt={label} style={{ width: 32, height: 32, objectFit: 'contain' }} />
|
||||
) : (
|
||||
<span style={{ opacity: 0.35 }}>{defaultSvg}</span>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>{label}</div>
|
||||
<label className="btn btn-secondary btn-sm" style={{ cursor: 'pointer', display: 'inline-block' }}>
|
||||
Upload PNG
|
||||
<input type="file" accept="image/png,image/svg+xml,image/*" style={{ display: 'none' }} onChange={handleUpload} />
|
||||
</label>
|
||||
{currentUrl && (
|
||||
<span style={{ marginLeft: 8, fontSize: 12, color: 'var(--text-tertiary)' }}>Custom icon active</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SettingsModal({ onClose }) {
|
||||
const toast = useToast();
|
||||
const [settings, setSettings] = useState({});
|
||||
@@ -58,11 +13,11 @@ export default function SettingsModal({ onClose }) {
|
||||
useEffect(() => {
|
||||
api.getSettings().then(({ settings }) => {
|
||||
setSettings(settings);
|
||||
setAppName(settings.app_name || 'TeamChat');
|
||||
setAppName(settings.app_name || 'jama');
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const notifySidebarRefresh = () => window.dispatchEvent(new Event('teamchat:settings-changed'));
|
||||
const notifySidebarRefresh = () => window.dispatchEvent(new Event('jama:settings-changed'));
|
||||
|
||||
const handleSaveName = async () => {
|
||||
if (!appName.trim()) return;
|
||||
@@ -93,18 +48,13 @@ export default function SettingsModal({ onClose }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleIconUploaded = (key, url) => {
|
||||
setSettings(prev => ({ ...prev, [key]: url }));
|
||||
notifySidebarRefresh();
|
||||
};
|
||||
|
||||
const handleReset = async () => {
|
||||
setResetting(true);
|
||||
try {
|
||||
await api.resetSettings();
|
||||
const { settings: fresh } = await api.getSettings();
|
||||
setSettings(fresh);
|
||||
setAppName(fresh.app_name || 'TeamChat');
|
||||
setAppName(fresh.app_name || 'jama');
|
||||
toast('Settings reset to defaults', 'success');
|
||||
notifySidebarRefresh();
|
||||
setShowResetConfirm(false);
|
||||
@@ -115,18 +65,6 @@ export default function SettingsModal({ onClose }) {
|
||||
}
|
||||
};
|
||||
|
||||
const newChatSvg = (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||
<line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/>
|
||||
</svg>
|
||||
);
|
||||
const groupInfoSvg = (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={e => e.target === e.currentTarget && onClose()}>
|
||||
<div className="modal" style={{ maxWidth: 460 }}>
|
||||
@@ -146,23 +84,20 @@ export default function SettingsModal({ onClose }) {
|
||||
border: '1px solid var(--border)', overflow: 'hidden', display: 'flex',
|
||||
alignItems: 'center', justifyContent: 'center', flexShrink: 0
|
||||
}}>
|
||||
{settings.logo_url ? (
|
||||
<img src={settings.logo_url} alt="logo" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
) : (
|
||||
<svg viewBox="0 0 48 48" fill="none" style={{ width: 48, height: 48 }}>
|
||||
<circle cx="24" cy="24" r="24" fill="#1a73e8"/>
|
||||
<path d="M12 16h24v2H12zM12 22h18v2H12zM12 28h20v2H12z" fill="white"/>
|
||||
<circle cx="36" cy="32" r="8" fill="#34a853"/>
|
||||
<path d="M33 32l2 2 4-4" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)}
|
||||
<img
|
||||
src={settings.logo_url || '/icons/jama.png'}
|
||||
alt="logo"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="btn btn-secondary btn-sm" style={{ cursor: 'pointer', display: 'inline-block' }}>
|
||||
Upload Logo
|
||||
<input type="file" accept="image/*" style={{ display: 'none' }} onChange={handleLogoUpload} />
|
||||
</label>
|
||||
<p style={{ fontSize: 12, color: 'var(--text-tertiary)', marginTop: 6 }}>Square format, max 1MB. Used in sidebar, login page and browser tab.</p>
|
||||
<p style={{ fontSize: 12, color: 'var(--text-tertiary)', marginTop: 6 }}>
|
||||
Square format, max 1MB. Used in sidebar, login page and browser tab.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,58 +113,37 @@ export default function SettingsModal({ onClose }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom Icons */}
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div className="settings-section-label">Interface Icons</div>
|
||||
<IconUploadRow
|
||||
label="New Chat Button"
|
||||
settingKey="icon_newchat"
|
||||
currentUrl={settings.icon_newchat}
|
||||
onUploaded={handleIconUploaded}
|
||||
defaultSvg={newChatSvg}
|
||||
/>
|
||||
<IconUploadRow
|
||||
label="Group Info Button"
|
||||
settingKey="icon_groupinfo"
|
||||
currentUrl={settings.icon_groupinfo}
|
||||
onUploaded={handleIconUploaded}
|
||||
defaultSvg={groupInfoSvg}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Reset + Version */}
|
||||
<div style={{ marginBottom: settings.pw_reset_active === 'true' ? 16 : 0 }}>
|
||||
<div className="settings-section-label">Reset</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
|
||||
{!showResetConfirm ? (
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => setShowResetConfirm(true)}>
|
||||
Reset All to Defaults
|
||||
</button>
|
||||
) : (
|
||||
<div style={{
|
||||
background: '#fce8e6', border: '1px solid #f5c6c2',
|
||||
borderRadius: 'var(--radius)', padding: '12px 14px'
|
||||
}}>
|
||||
<p style={{ fontSize: 13, color: 'var(--error)', marginBottom: 12 }}>
|
||||
This will reset the app name, logo, and all custom icons to their install defaults. This cannot be undone.
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<button className="btn btn-sm" style={{ background: 'var(--error)', color: 'white' }} onClick={handleReset} disabled={resetting}>
|
||||
{resetting ? 'Resetting...' : 'Yes, Reset Everything'}
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => setShowResetConfirm(false)}>
|
||||
Cancel
|
||||
</button>
|
||||
{!showResetConfirm ? (
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => setShowResetConfirm(true)}>
|
||||
Reset All to Defaults
|
||||
</button>
|
||||
) : (
|
||||
<div style={{
|
||||
background: '#fce8e6', border: '1px solid #f5c6c2',
|
||||
borderRadius: 'var(--radius)', padding: '12px 14px'
|
||||
}}>
|
||||
<p style={{ fontSize: 13, color: 'var(--error)', marginBottom: 12 }}>
|
||||
This will reset the app name and logo to their install defaults. This cannot be undone.
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<button className="btn btn-sm" style={{ background: 'var(--error)', color: 'white' }} onClick={handleReset} disabled={resetting}>
|
||||
{resetting ? 'Resetting...' : 'Yes, Reset Everything'}
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => setShowResetConfirm(false)}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{settings.app_version && (
|
||||
<span style={{ fontSize: 11, color: 'var(--text-tertiary)', whiteSpace: 'nowrap' }}>
|
||||
v{settings.app_version}
|
||||
</span>
|
||||
)}
|
||||
</div>{/* end flex row */}
|
||||
</div>{/* end Reset section */}
|
||||
)}
|
||||
{settings.app_version && (
|
||||
<span style={{ fontSize: 11, color: 'var(--text-tertiary)', whiteSpace: 'nowrap' }}>
|
||||
v{settings.app_version}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{settings.pw_reset_active === 'true' && (
|
||||
<div className="warning-banner">
|
||||
|
||||
Reference in New Issue
Block a user