feat: Replaced all 28 JS hover handlers in Settings.tsx with CSS utilit…
- "src/frontend/src/pages/Settings.tsx" - "src/frontend/src/styles/global.css" - "src/frontend/src/components/Skeleton.tsx" GSD-Task: S02/T03
This commit is contained in:
parent
3355326526
commit
538f9ec69b
3 changed files with 117 additions and 229 deletions
|
|
@ -174,6 +174,65 @@ export function SkeletonSystem() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Skeleton for the settings page. */
|
||||||
|
export function SkeletonSettings() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Skeleton width={120} height={28} style={{ marginBottom: 'var(--space-6)' }} />
|
||||||
|
|
||||||
|
{/* General section */}
|
||||||
|
<div style={{ marginBottom: 'var(--space-8)' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', marginBottom: 'var(--space-4)' }}>
|
||||||
|
<Skeleton width={20} height={20} borderRadius="var(--radius-sm)" />
|
||||||
|
<Skeleton width={80} height={20} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--bg-card)',
|
||||||
|
borderRadius: 'var(--radius-xl)',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
padding: 'var(--space-4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Array.from({ length: 4 }).map((_, i) => (
|
||||||
|
<div key={i} style={{ display: 'flex', gap: 'var(--space-4)', marginBottom: i < 3 ? 'var(--space-4)' : 0, alignItems: 'center' }}>
|
||||||
|
<Skeleton width={140} height={14} />
|
||||||
|
<Skeleton width={250} height={32} borderRadius="var(--radius-md)" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Platform Settings section */}
|
||||||
|
<div style={{ marginBottom: 'var(--space-8)' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', marginBottom: 'var(--space-4)' }}>
|
||||||
|
<Skeleton width={20} height={20} borderRadius="var(--radius-sm)" />
|
||||||
|
<Skeleton width={140} height={20} />
|
||||||
|
</div>
|
||||||
|
<SkeletonTable rows={2} columns={6} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Format Profiles section */}
|
||||||
|
<div style={{ marginBottom: 'var(--space-8)' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--space-4)' }}>
|
||||||
|
<Skeleton width={130} height={20} />
|
||||||
|
<Skeleton width={110} height={32} borderRadius="var(--radius-md)" />
|
||||||
|
</div>
|
||||||
|
<SkeletonTable rows={3} columns={6} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notifications section */}
|
||||||
|
<div style={{ marginBottom: 'var(--space-8)' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--space-4)' }}>
|
||||||
|
<Skeleton width={120} height={20} />
|
||||||
|
<Skeleton width={110} height={32} borderRadius="var(--radius-md)" />
|
||||||
|
</div>
|
||||||
|
<SkeletonTable rows={2} columns={5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Skeleton for the channels list page. */
|
/** Skeleton for the channels list page. */
|
||||||
export function SkeletonChannelsList({ rows = 4 }: { rows?: number }) {
|
export function SkeletonChannelsList({ rows = 4 }: { rows?: number }) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { Modal } from '../components/Modal';
|
||||||
import { FormatProfileForm, type FormatProfileFormValues } from '../components/FormatProfileForm';
|
import { FormatProfileForm, type FormatProfileFormValues } from '../components/FormatProfileForm';
|
||||||
import { PlatformSettingsForm, type PlatformSettingsFormValues } from '../components/PlatformSettingsForm';
|
import { PlatformSettingsForm, type PlatformSettingsFormValues } from '../components/PlatformSettingsForm';
|
||||||
import { NotificationForm, type NotificationFormValues } from '../components/NotificationForm';
|
import { NotificationForm, type NotificationFormValues } from '../components/NotificationForm';
|
||||||
|
import { SkeletonSettings } from '../components/Skeleton';
|
||||||
import type { FormatProfile, PlatformSettings } from '@shared/types/index';
|
import type { FormatProfile, PlatformSettings } from '@shared/types/index';
|
||||||
|
|
||||||
// ── Badge styles ──
|
// ── Badge styles ──
|
||||||
|
|
@ -40,17 +41,6 @@ const badgeBase: React.CSSProperties = {
|
||||||
letterSpacing: '0.04em',
|
letterSpacing: '0.04em',
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconButtonBase: React.CSSProperties = {
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
borderRadius: 'var(--radius-sm)',
|
|
||||||
color: 'var(--text-muted)',
|
|
||||||
transition: 'color var(--transition-fast), background-color var(--transition-fast)',
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Component ──
|
// ── Component ──
|
||||||
|
|
||||||
export function SettingsPage() {
|
export function SettingsPage() {
|
||||||
|
|
@ -297,15 +287,7 @@ export function SettingsPage() {
|
||||||
onClick={(e) => { e.stopPropagation(); setEditingPlatform(row.platform); }}
|
onClick={(e) => { e.stopPropagation(); setEditingPlatform(row.platform); }}
|
||||||
title={`Edit ${row.label} settings`}
|
title={`Edit ${row.label} settings`}
|
||||||
aria-label={`Edit ${row.label} settings`}
|
aria-label={`Edit ${row.label} settings`}
|
||||||
style={iconButtonBase}
|
className="btn-icon btn-icon-edit"
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--accent)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--accent-subtle)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -479,15 +461,7 @@ export function SettingsPage() {
|
||||||
onClick={(e) => { e.stopPropagation(); setEditingProfile(p); }}
|
onClick={(e) => { e.stopPropagation(); setEditingProfile(p); }}
|
||||||
title="Edit profile"
|
title="Edit profile"
|
||||||
aria-label={`Edit ${p.name}`}
|
aria-label={`Edit ${p.name}`}
|
||||||
style={iconButtonBase}
|
className="btn-icon btn-icon-edit"
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--accent)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--accent-subtle)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -496,15 +470,7 @@ export function SettingsPage() {
|
||||||
onClick={(e) => { e.stopPropagation(); setDeletingProfile(p); }}
|
onClick={(e) => { e.stopPropagation(); setDeletingProfile(p); }}
|
||||||
title="Delete profile"
|
title="Delete profile"
|
||||||
aria-label={`Delete ${p.name}`}
|
aria-label={`Delete ${p.name}`}
|
||||||
style={iconButtonBase}
|
className="btn-icon btn-icon-delete"
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--danger)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--danger-bg)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -587,18 +553,8 @@ export function SettingsPage() {
|
||||||
title="Send test notification"
|
title="Send test notification"
|
||||||
aria-label={`Test ${n.name}`}
|
aria-label={`Test ${n.name}`}
|
||||||
disabled={result === 'loading'}
|
disabled={result === 'loading'}
|
||||||
style={{
|
className="btn-icon btn-icon-test"
|
||||||
...iconButtonBase,
|
style={{ opacity: result === 'loading' ? 0.5 : 1 }}
|
||||||
opacity: result === 'loading' ? 0.5 : 1,
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--success)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--success-bg)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{result === 'loading'
|
{result === 'loading'
|
||||||
? <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />
|
? <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />
|
||||||
|
|
@ -611,15 +567,7 @@ export function SettingsPage() {
|
||||||
onClick={(e) => { e.stopPropagation(); setEditingNotification(n); }}
|
onClick={(e) => { e.stopPropagation(); setEditingNotification(n); }}
|
||||||
title="Edit channel"
|
title="Edit channel"
|
||||||
aria-label={`Edit ${n.name}`}
|
aria-label={`Edit ${n.name}`}
|
||||||
style={iconButtonBase}
|
className="btn-icon btn-icon-edit"
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--accent)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--accent-subtle)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -629,15 +577,7 @@ export function SettingsPage() {
|
||||||
onClick={(e) => { e.stopPropagation(); setDeletingNotification(n); }}
|
onClick={(e) => { e.stopPropagation(); setDeletingNotification(n); }}
|
||||||
title="Delete channel"
|
title="Delete channel"
|
||||||
aria-label={`Delete ${n.name}`}
|
aria-label={`Delete ${n.name}`}
|
||||||
style={iconButtonBase}
|
className="btn-icon btn-icon-delete"
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--danger)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--danger-bg)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -652,12 +592,7 @@ export function SettingsPage() {
|
||||||
// ── Loading state ──
|
// ── Loading state ──
|
||||||
|
|
||||||
if (profilesLoading) {
|
if (profilesLoading) {
|
||||||
return (
|
return <SkeletonSettings />;
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 'var(--space-12)', color: 'var(--text-muted)' }}>
|
|
||||||
<Loader size={20} style={{ animation: 'spin 1s linear infinite', marginRight: 'var(--space-3)' }} />
|
|
||||||
Loading settings...
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Error state ──
|
// ── Error state ──
|
||||||
|
|
@ -683,17 +618,7 @@ export function SettingsPage() {
|
||||||
<button
|
<button
|
||||||
onClick={() => refetchProfiles()}
|
onClick={() => refetchProfiles()}
|
||||||
aria-label="Retry"
|
aria-label="Retry"
|
||||||
style={{
|
className="btn btn-danger"
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 'var(--space-2)',
|
|
||||||
padding: 'var(--space-2) var(--space-3)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--danger)',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<RefreshCw size={14} />
|
<RefreshCw size={14} />
|
||||||
Retry
|
Retry
|
||||||
|
|
@ -774,15 +699,7 @@ export function SettingsPage() {
|
||||||
onClick={() => setShowApiKey((v) => !v)}
|
onClick={() => setShowApiKey((v) => !v)}
|
||||||
title={showApiKey ? 'Hide API key' : 'Show API key'}
|
title={showApiKey ? 'Hide API key' : 'Show API key'}
|
||||||
aria-label={showApiKey ? 'Hide API key' : 'Show API key'}
|
aria-label={showApiKey ? 'Hide API key' : 'Show API key'}
|
||||||
style={iconButtonBase}
|
className="btn-icon btn-icon-edit"
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--accent)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--accent-subtle)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{showApiKey ? <EyeOff size={14} /> : <Eye size={14} />}
|
{showApiKey ? <EyeOff size={14} /> : <Eye size={14} />}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -792,22 +709,8 @@ export function SettingsPage() {
|
||||||
onClick={handleCopyApiKey}
|
onClick={handleCopyApiKey}
|
||||||
title={copySuccess ? 'Copied!' : 'Copy to clipboard'}
|
title={copySuccess ? 'Copied!' : 'Copy to clipboard'}
|
||||||
aria-label="Copy API key to clipboard"
|
aria-label="Copy API key to clipboard"
|
||||||
style={{
|
className="btn-icon btn-icon-edit"
|
||||||
...iconButtonBase,
|
style={copySuccess ? { color: 'var(--success)' } : undefined}
|
||||||
color: copySuccess ? 'var(--success)' : 'var(--text-muted)',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
if (!copySuccess) {
|
|
||||||
e.currentTarget.style.color = 'var(--accent)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--accent-subtle)';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
if (!copySuccess) {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{copySuccess ? <CheckCircle size={14} /> : <Copy size={14} />}
|
{copySuccess ? <CheckCircle size={14} /> : <Copy size={14} />}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -817,15 +720,7 @@ export function SettingsPage() {
|
||||||
onClick={() => setShowRegenerateConfirm(true)}
|
onClick={() => setShowRegenerateConfirm(true)}
|
||||||
title="Regenerate API key"
|
title="Regenerate API key"
|
||||||
aria-label="Regenerate API key"
|
aria-label="Regenerate API key"
|
||||||
style={iconButtonBase}
|
className="btn-icon btn-icon-warning"
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--warning)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'var(--warning-bg)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = 'var(--text-muted)';
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<RotateCw size={14} />
|
<RotateCw size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -911,17 +806,10 @@ export function SettingsPage() {
|
||||||
<button
|
<button
|
||||||
onClick={handleSaveSettings}
|
onClick={handleSaveSettings}
|
||||||
disabled={!settingsDirty || !settingsValid || updateAppSettingsMutation.isPending}
|
disabled={!settingsDirty || !settingsValid || updateAppSettingsMutation.isPending}
|
||||||
|
className={`btn ${settingsSaveFlash ? 'btn-primary' : 'btn-primary'}`}
|
||||||
style={{
|
style={{
|
||||||
display: 'inline-flex',
|
backgroundColor: settingsSaveFlash ? 'var(--success)' : undefined,
|
||||||
alignItems: 'center',
|
borderColor: settingsSaveFlash ? 'var(--success)' : undefined,
|
||||||
gap: 'var(--space-2)',
|
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: settingsSaveFlash ? 'var(--success)' : 'var(--accent)',
|
|
||||||
color: 'var(--text-inverse)',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 600,
|
|
||||||
transition: 'background-color var(--transition-fast), opacity var(--transition-fast)',
|
|
||||||
opacity: !settingsDirty || !settingsValid ? 0.5 : 1,
|
opacity: !settingsDirty || !settingsValid ? 0.5 : 1,
|
||||||
cursor: !settingsDirty || !settingsValid ? 'not-allowed' : 'pointer',
|
cursor: !settingsDirty || !settingsValid ? 'not-allowed' : 'pointer',
|
||||||
}}
|
}}
|
||||||
|
|
@ -992,20 +880,7 @@ export function SettingsPage() {
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCreateProfileModal(true)}
|
onClick={() => setShowCreateProfileModal(true)}
|
||||||
style={{
|
className="btn btn-primary"
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 'var(--space-2)',
|
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--accent)',
|
|
||||||
color: 'var(--text-inverse)',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 600,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = 'var(--accent-hover)')}
|
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = 'var(--accent)')}
|
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
Add Profile
|
Add Profile
|
||||||
|
|
@ -1045,20 +920,7 @@ export function SettingsPage() {
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCreateNotifModal(true)}
|
onClick={() => setShowCreateNotifModal(true)}
|
||||||
style={{
|
className="btn btn-primary"
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 'var(--space-2)',
|
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--accent)',
|
|
||||||
color: 'var(--text-inverse)',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 600,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = 'var(--accent-hover)')}
|
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = 'var(--accent)')}
|
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
Add Channel
|
Add Channel
|
||||||
|
|
@ -1148,36 +1010,15 @@ export function SettingsPage() {
|
||||||
<button
|
<button
|
||||||
onClick={() => setDeletingProfile(null)}
|
onClick={() => setDeletingProfile(null)}
|
||||||
disabled={deleteProfileMutation.isPending}
|
disabled={deleteProfileMutation.isPending}
|
||||||
style={{
|
className="btn btn-ghost"
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--bg-hover)',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 500,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-selected)')}
|
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-hover)')}
|
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDeleteProfile}
|
onClick={handleDeleteProfile}
|
||||||
disabled={deleteProfileMutation.isPending}
|
disabled={deleteProfileMutation.isPending}
|
||||||
style={{
|
className="btn btn-danger"
|
||||||
display: 'inline-flex',
|
style={{ opacity: deleteProfileMutation.isPending ? 0.6 : 1 }}
|
||||||
alignItems: 'center',
|
|
||||||
gap: 'var(--space-2)',
|
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--danger)',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 600,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
opacity: deleteProfileMutation.isPending ? 0.6 : 1,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{deleteProfileMutation.isPending && <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />}
|
{deleteProfileMutation.isPending && <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />}
|
||||||
Delete
|
Delete
|
||||||
|
|
@ -1271,36 +1112,15 @@ export function SettingsPage() {
|
||||||
<button
|
<button
|
||||||
onClick={() => setDeletingNotification(null)}
|
onClick={() => setDeletingNotification(null)}
|
||||||
disabled={deleteNotifMutation.isPending}
|
disabled={deleteNotifMutation.isPending}
|
||||||
style={{
|
className="btn btn-ghost"
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--bg-hover)',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 500,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-selected)')}
|
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-hover)')}
|
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDeleteNotification}
|
onClick={handleDeleteNotification}
|
||||||
disabled={deleteNotifMutation.isPending}
|
disabled={deleteNotifMutation.isPending}
|
||||||
style={{
|
className="btn btn-danger"
|
||||||
display: 'inline-flex',
|
style={{ opacity: deleteNotifMutation.isPending ? 0.6 : 1 }}
|
||||||
alignItems: 'center',
|
|
||||||
gap: 'var(--space-2)',
|
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--danger)',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 600,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
opacity: deleteNotifMutation.isPending ? 0.6 : 1,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{deleteNotifMutation.isPending && <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />}
|
{deleteNotifMutation.isPending && <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />}
|
||||||
Delete
|
Delete
|
||||||
|
|
@ -1338,36 +1158,15 @@ export function SettingsPage() {
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowRegenerateConfirm(false)}
|
onClick={() => setShowRegenerateConfirm(false)}
|
||||||
disabled={regenerateApiKeyMutation.isPending}
|
disabled={regenerateApiKeyMutation.isPending}
|
||||||
style={{
|
className="btn btn-ghost"
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--bg-hover)',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 500,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-selected)')}
|
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-hover)')}
|
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleRegenerateApiKey}
|
onClick={handleRegenerateApiKey}
|
||||||
disabled={regenerateApiKeyMutation.isPending}
|
disabled={regenerateApiKeyMutation.isPending}
|
||||||
style={{
|
className="btn btn-warning"
|
||||||
display: 'inline-flex',
|
style={{ opacity: regenerateApiKeyMutation.isPending ? 0.6 : 1 }}
|
||||||
alignItems: 'center',
|
|
||||||
gap: 'var(--space-2)',
|
|
||||||
padding: 'var(--space-2) var(--space-4)',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
backgroundColor: 'var(--warning)',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 'var(--font-size-sm)',
|
|
||||||
fontWeight: 600,
|
|
||||||
transition: 'background-color var(--transition-fast)',
|
|
||||||
opacity: regenerateApiKeyMutation.isPending ? 0.6 : 1,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{regenerateApiKeyMutation.isPending && <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />}
|
{regenerateApiKeyMutation.isPending && <Loader size={14} style={{ animation: 'spin 1s linear infinite' }} />}
|
||||||
Regenerate
|
Regenerate
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,36 @@ tr:hover {
|
||||||
background-color: var(--bg-hover);
|
background-color: var(--bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-icon-edit:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
background-color: var(--accent-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-delete:hover {
|
||||||
|
color: var(--danger);
|
||||||
|
background-color: var(--danger-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-test:hover {
|
||||||
|
color: var(--success);
|
||||||
|
background-color: var(--success-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon-warning:hover {
|
||||||
|
color: var(--warning);
|
||||||
|
background-color: var(--warning-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background-color: var(--warning);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning:hover:not(:disabled) {
|
||||||
|
filter: brightness(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Modal animation ── */
|
/* ── Modal animation ── */
|
||||||
@keyframes modal-enter {
|
@keyframes modal-enter {
|
||||||
from {
|
from {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue