feat(config): split profile into Personal Info + Preferences sections, centered
All checks were successful
Beta Release / beta (push) Successful in 40s
All checks were successful
Beta Release / beta (push) Successful in 40s
- Profile panel now shows two distinct cards with section titles - Personal Info (name, pseudo, email, languages) and Preferences (editor, shell, theme, etc.) are visually separated - Content centered with max-width 540px - Added i18n keys for section titles 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
@@ -220,16 +220,34 @@ function PanelProfile({ config, editProfile, profileForm, setProfileForm, setEdi
|
|||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return (
|
return (
|
||||||
|
<div className="config-profile-center">
|
||||||
<div className="config-card">
|
<div className="config-card">
|
||||||
<div className="empty-state">{t('config.loadingProfile')}</div>
|
<div className="empty-state">{t('config.loadingProfile')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const personalKeys = Object.entries(profile).filter(([k, v]) => k !== 'preferences' && typeof v !== 'object')
|
||||||
|
const personalObj = Object.fromEntries(personalKeys)
|
||||||
|
const preferences = profile.preferences || null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="config-profile-center">
|
||||||
<div className="config-card">
|
<div className="config-card">
|
||||||
<RenderFields obj={profile} path="" editing={editProfile} onChange={updateField} t={t} />
|
<div className="section-title">{t('config.profileInfo') || 'Informations personnelles'}</div>
|
||||||
<div className="config-card-actions">
|
<RenderFields obj={personalObj} path="" editing={editProfile} onChange={updateField} t={t} />
|
||||||
|
</div>
|
||||||
|
<div className="config-card">
|
||||||
|
<div className="section-title">{t('config.profilePrefs') || 'Préférences'}</div>
|
||||||
|
{preferences ? (
|
||||||
|
<RenderFields obj={preferences} path="preferences" editing={editProfile} onChange={updateField} t={t} />
|
||||||
|
) : (
|
||||||
|
<div className="config-card-row"><span className="config-card-value" style={{ color: 'var(--text-disabled)' }}>—</span></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="config-card">
|
||||||
|
<div className="config-card-actions" style={{ justifyContent: 'center' }}>
|
||||||
{editProfile ? (
|
{editProfile ? (
|
||||||
<>
|
<>
|
||||||
<button className="primary sm" onClick={handleSaveProfile}>{t('config.save')}</button>
|
<button className="primary sm" onClick={handleSaveProfile}>{t('config.save')}</button>
|
||||||
@@ -243,25 +261,17 @@ function PanelProfile({ config, editProfile, profileForm, setProfileForm, setEdi
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RenderFields({ obj, path, editing, onChange, t }) {
|
function RenderFields({ obj, path, editing, onChange, t }) {
|
||||||
if (!obj || typeof obj !== 'object') return null
|
if (!obj || typeof obj !== 'object') return null
|
||||||
|
|
||||||
return Object.entries(obj).map(([key, value]) => {
|
return Object.entries(obj).filter(([, v]) => v === null || typeof v !== 'object').map(([key, value]) => {
|
||||||
const fieldPath = path ? `${path}.${key}` : key
|
const fieldPath = path ? `${path}.${key}` : key
|
||||||
const label = getFieldLabel(key, t)
|
const label = getFieldLabel(key, t)
|
||||||
|
|
||||||
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
||||||
return (
|
|
||||||
<div key={key} className="config-card-group">
|
|
||||||
<span className="config-card-group-label">{label}</span>
|
|
||||||
<RenderFields obj={value} path={fieldPath} editing={editing} onChange={onChange} t={t} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editing) {
|
if (editing) {
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ const en = {
|
|||||||
installed: 'Installed',
|
installed: 'Installed',
|
||||||
missing: 'Missing',
|
missing: 'Missing',
|
||||||
editProfile: 'Edit',
|
editProfile: 'Edit',
|
||||||
|
profileInfo: 'Personal Info',
|
||||||
|
profilePrefs: 'Preferences',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
editProvider: 'Configure',
|
editProvider: 'Configure',
|
||||||
validateKey: 'Validate',
|
validateKey: 'Validate',
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ const fr = {
|
|||||||
installed: 'Install\u00e9',
|
installed: 'Install\u00e9',
|
||||||
missing: 'Manquant',
|
missing: 'Manquant',
|
||||||
editProfile: 'Modifier',
|
editProfile: 'Modifier',
|
||||||
|
profileInfo: 'Informations personnelles',
|
||||||
|
profilePrefs: 'Préférences',
|
||||||
editProvider: 'Configurer',
|
editProvider: 'Configurer',
|
||||||
validateKey: 'Valider',
|
validateKey: 'Valider',
|
||||||
validating: 'V\u00e9rification...',
|
validating: 'V\u00e9rification...',
|
||||||
|
|||||||
@@ -435,6 +435,10 @@ input::placeholder { color: var(--text-disabled); }
|
|||||||
|
|
||||||
.config-panel-area { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
.config-panel-area { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||||
.config-panel-body { flex: 1; overflow-y: auto; padding: 16px 28px 28px; }
|
.config-panel-body { flex: 1; overflow-y: auto; padding: 16px 28px 28px; }
|
||||||
|
.config-profile-center {
|
||||||
|
max-width: 540px; margin: 0 auto; width: 100%;
|
||||||
|
display: flex; flex-direction: column; gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.config-card {
|
.config-card {
|
||||||
background: var(--bg-card); border: 1px solid var(--border);
|
background: var(--bg-card); border: 1px solid var(--border);
|
||||||
@@ -622,6 +626,7 @@ input::placeholder { color: var(--text-disabled); }
|
|||||||
.dash-cmd-text {
|
.dash-cmd-text {
|
||||||
font-size: 11px; font-family: var(--font-mono); color: var(--text-secondary);
|
font-size: 11px; font-family: var(--font-mono); color: var(--text-secondary);
|
||||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
flex: 1; min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Services */
|
/* Services */
|
||||||
|
|||||||
Reference in New Issue
Block a user