fix(ui): theme tokens, primary CTAs, billing; invitations; connections
Made-with: Cursor
This commit is contained in:
parent
d3d054b132
commit
2a60f322ca
20 changed files with 175 additions and 114 deletions
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
.treeNode.multiSelected {
|
||||
background: var(--color-bg-multi-selected, rgba(25, 118, 210, 0.14));
|
||||
box-shadow: inset 3px 0 0 var(--color-primary, #1976d2);
|
||||
box-shadow: inset 3px 0 0 var(--color-primary, #F25843);
|
||||
}
|
||||
|
||||
.treeNode.multiSelected:hover {
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
.treeNode.dropTarget {
|
||||
background: var(--color-bg-drop, rgba(25, 118, 210, 0.15));
|
||||
outline: 2px dashed var(--color-primary, #1976d2);
|
||||
outline: 2px dashed var(--color-primary, #F25843);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
|
||||
.renameInput {
|
||||
flex: 1;
|
||||
border: 1px solid var(--color-primary, #1976d2);
|
||||
border: 1px solid var(--color-primary, #F25843);
|
||||
border-radius: 3px;
|
||||
padding: 1px 4px;
|
||||
font-size: inherit;
|
||||
|
|
|
|||
|
|
@ -243,9 +243,9 @@ const FilesTab: React.FC<FilesTabProps> = ({ context, onFileSelect }) => {
|
|||
<div style={{
|
||||
position: 'absolute', inset: 0,
|
||||
background: 'rgba(25, 118, 210, 0.08)',
|
||||
border: '2px dashed #1976d2', borderRadius: 8,
|
||||
border: '2px dashed #F25843', borderRadius: 8,
|
||||
zIndex: 10, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 13, fontWeight: 600, color: '#1976d2',
|
||||
fontSize: 13, fontWeight: 600, color: '#F25843',
|
||||
}}>
|
||||
Dateien hier ablegen
|
||||
</div>
|
||||
|
|
@ -257,14 +257,14 @@ const FilesTab: React.FC<FilesTabProps> = ({ context, onFileSelect }) => {
|
|||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={uploading}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: '#1976d2' }}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: '#F25843' }}
|
||||
title="Upload files"
|
||||
>
|
||||
{uploading ? '...' : '+'}
|
||||
</button>
|
||||
<button
|
||||
onClick={_refreshAll}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: '#1976d2' }}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: '#F25843' }}
|
||||
>
|
||||
{'\u21BB'}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -142,12 +142,12 @@ const _SOURCE_COLORS: Record<string, string> = {
|
|||
ftpFolder: '#795548',
|
||||
files: '#795548',
|
||||
'local:ftp': '#795548',
|
||||
'local:jira': '#1976d2',
|
||||
'local:jira': '#0052CC',
|
||||
clickup: '#7b68ee',
|
||||
};
|
||||
|
||||
function _getSourceColor(sourceType: string): string {
|
||||
return _SOURCE_COLORS[sourceType] || '#1976d2';
|
||||
return _SOURCE_COLORS[sourceType] || '#F25843';
|
||||
}
|
||||
|
||||
const _SOURCE_ICONS: Record<string, string> = {
|
||||
|
|
@ -335,7 +335,7 @@ function _Spinner(): React.ReactElement {
|
|||
return (
|
||||
<span style={{
|
||||
display: 'inline-block', width: 10, height: 10,
|
||||
border: '1.5px solid #ccc', borderTopColor: '#1976d2',
|
||||
border: '1.5px solid var(--border-color, #ccc)', borderTopColor: 'var(--primary-color, #F25843)',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 0.6s linear infinite',
|
||||
}} />
|
||||
|
|
@ -876,7 +876,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged }) =>
|
|||
<button
|
||||
onClick={_loadConnections}
|
||||
disabled={loadingRoot}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: '#1976d2' }}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: 'var(--primary-color, #F25843)' }}
|
||||
>
|
||||
{loadingRoot ? '...' : '\u21BB'}
|
||||
</button>
|
||||
|
|
@ -1157,9 +1157,9 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
|
|||
onClick={e => { e.stopPropagation(); onAdd(node); }}
|
||||
disabled={isAdding}
|
||||
style={{
|
||||
background: 'none', border: '1px solid #1976d2', borderRadius: 3,
|
||||
background: 'none', border: '1px solid var(--primary-color, #F25843)', borderRadius: 3,
|
||||
cursor: isAdding ? 'not-allowed' : 'pointer',
|
||||
fontSize: 10, color: '#1976d2', padding: '1px 5px',
|
||||
fontSize: 10, color: 'var(--primary-color, #F25843)', padding: '1px 5px',
|
||||
opacity: isAdding ? 0.5 : 1,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ export function useConfirm() {
|
|||
style={{
|
||||
padding: '8px 18px', borderRadius: '6px', fontSize: '0.875rem', fontWeight: 600,
|
||||
border: 'none',
|
||||
background: isDanger ? '#ef4444' : 'var(--color-primary, #3b82f6)',
|
||||
background: isDanger ? '#ef4444' : 'var(--primary-color, #F25843)',
|
||||
color: '#fff',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export interface Invitation {
|
|||
export interface InvitationCreate {
|
||||
/** Username of the user to invite (optional when email is provided) */
|
||||
targetUsername?: string;
|
||||
/** Email address to send invitation link (required for new users) */
|
||||
/** Email to send invitation link; optional if targetUsername is set */
|
||||
email?: string;
|
||||
roleIds: string[];
|
||||
featureInstanceId?: string;
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export function usePrompt() {
|
|||
style={{
|
||||
padding: '8px 18px', borderRadius: '6px', fontSize: '0.875rem', fontWeight: 600,
|
||||
border: 'none',
|
||||
background: isDanger ? '#ef4444' : 'var(--color-primary, #3b82f6)',
|
||||
background: isDanger ? '#ef4444' : 'var(--primary-color, #F25843)',
|
||||
color: '#fff',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@
|
|||
/* Let child components handle their own scrolling for sticky headers */
|
||||
overflow: hidden;
|
||||
background: var(--bg-primary, #ffffff);
|
||||
color: var(--text-primary, #1a1a1a);
|
||||
}
|
||||
|
||||
/* Fills .content flex column so admin pages get a bounded height for inner scroll */
|
||||
|
|
@ -168,6 +169,7 @@
|
|||
|
||||
:global(.dark-theme) .content {
|
||||
background: var(--bg-dark, #0a0a0a);
|
||||
color: var(--text-primary, #e5e7eb);
|
||||
}
|
||||
|
||||
:global(.dark-theme) .mobileMenuButton {
|
||||
|
|
|
|||
|
|
@ -550,8 +550,8 @@
|
|||
|
||||
.statusBadge.starting,
|
||||
.statusBadge.running {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
background: var(--primary-dark-bg, rgba(242, 88, 67, 0.12));
|
||||
color: var(--primary-color, #F25843);
|
||||
}
|
||||
|
||||
.statusBadge.completed {
|
||||
|
|
@ -617,7 +617,7 @@
|
|||
}
|
||||
|
||||
.logStatus {
|
||||
color: #1976d2;
|
||||
color: var(--primary-color, #F25843);
|
||||
}
|
||||
|
||||
.logEntryError .logStatus,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* 4-step invitation wizard:
|
||||
* 1. Choose invite type: "Invite to mandate" OR "Invite to feature instance"
|
||||
* 2. Select mandate (and feature instance if applicable)
|
||||
* 3. Add invitees (email required, username optional; existing users; role per invitee)
|
||||
* 3. Add invitees (mindestens E-Mail oder Benutzername für neue Benutzer; bestehende Benutzer; Rolle pro Einladung)
|
||||
* 4. Summary and send
|
||||
*/
|
||||
|
||||
|
|
@ -167,13 +167,24 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
|
||||
const addInviteeByEmail = () => {
|
||||
const email = inviteeForm.email.trim();
|
||||
if (!email) {
|
||||
setError('E-Mail ist erforderlich');
|
||||
const username = inviteeForm.username.trim();
|
||||
if (!email && !username) {
|
||||
setError('Bitte mindestens eine E-Mail-Adresse oder einen Benutzernamen angeben.');
|
||||
return;
|
||||
}
|
||||
const emailLower = email.toLowerCase();
|
||||
const userLower = username.toLowerCase();
|
||||
if (email && invitees.some(i => !i.isExisting && (i.email || '').toLowerCase() === emailLower)) {
|
||||
setError('Diese E-Mail ist bereits in der Liste');
|
||||
return;
|
||||
}
|
||||
if (username && invitees.some(i => !i.isExisting && (i.username || '').toLowerCase() === userLower)) {
|
||||
setError('Dieser Benutzername ist bereits in der Liste');
|
||||
return;
|
||||
}
|
||||
setInvitees(prev => [...prev, {
|
||||
email,
|
||||
username: undefined,
|
||||
username: username || undefined,
|
||||
roleIds: [...inviteeForm.roleIds],
|
||||
isExisting: false,
|
||||
}]);
|
||||
|
|
@ -189,10 +200,6 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
const user = allSystemUsers.find(u => u.id === selectedExistingUserId);
|
||||
if (!user) return;
|
||||
const email = (user.email || '').trim();
|
||||
if (!email) {
|
||||
setError('Dieser Benutzer hat keine E-Mail-Adresse');
|
||||
return;
|
||||
}
|
||||
if (invitees.some(i => i.userId === user.id)) {
|
||||
setError('Dieser Benutzer ist bereits in der Liste');
|
||||
return;
|
||||
|
|
@ -232,8 +239,9 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
const results: DispatchResult[] = [];
|
||||
try {
|
||||
for (const inv of invitees) {
|
||||
const emailTrim = (inv.email || '').trim();
|
||||
const payload = {
|
||||
email: inv.email,
|
||||
...(emailTrim ? { email: emailTrim } : {}),
|
||||
targetUsername: inv.username || undefined,
|
||||
roleIds: inv.roleIds,
|
||||
expiresInHours: EXPIRES_IN_HOURS,
|
||||
|
|
@ -244,14 +252,14 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
const result = await createInvitation(selectedMandate.id, payload);
|
||||
if (result.success) {
|
||||
results.push({
|
||||
email: inv.email,
|
||||
email: emailTrim,
|
||||
username: inv.username,
|
||||
success: true,
|
||||
emailSent: result.data?.emailSent,
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
email: inv.email,
|
||||
email: emailTrim,
|
||||
username: inv.username,
|
||||
success: false,
|
||||
error: result.error,
|
||||
|
|
@ -452,7 +460,7 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
<div style={_cardStyle}>
|
||||
<h3 style={{ margin: '0 0 8px 0', fontSize: '16px' }}>Einladungen hinzufügen</h3>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '13px', margin: '0 0 16px 0' }}>
|
||||
E-Mail ist erforderlich. Neue Benutzer legen ihren Benutzernamen beim Annehmen der Einladung selbst fest. Sie können neue Benutzer per E-Mail oder bestehende Benutzer hinzufügen.
|
||||
Für neue Benutzer: mindestens eine E-Mail <em>oder</em> ein Benutzername (vorgegeben). Ohne E-Mail wird kein Link per Mail versendet — der Einladungslink kann manuell geteilt werden. Bestehende Benutzer wählen Sie im zweiten Tab.
|
||||
</p>
|
||||
|
||||
{/* Add form: toggle email vs existing */}
|
||||
|
|
@ -462,7 +470,7 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
style={{ fontSize: '12px', padding: '6px 12px' }}
|
||||
onClick={() => setAddMode('email')}
|
||||
>
|
||||
Per E-Mail (neue Benutzer)
|
||||
Neue Benutzer (E-Mail und/oder Benutzername)
|
||||
</button>
|
||||
<button
|
||||
className={addMode === 'existing' ? styles.primaryButton : styles.secondaryButton}
|
||||
|
|
@ -476,7 +484,7 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
{addMode === 'email' ? (
|
||||
<div style={{ padding: '16px', background: 'var(--bg-secondary, #f8fafc)', borderRadius: '8px', marginBottom: '16px', display: 'grid', gap: '12px' }}>
|
||||
<div>
|
||||
<label className={`${styles.formLabel} ${styles.required}`}>E-Mail *</label>
|
||||
<label className={styles.formLabel}>E-Mail (optional)</label>
|
||||
<input
|
||||
className={styles.formInput}
|
||||
type="email"
|
||||
|
|
@ -484,8 +492,19 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
onChange={e => setInviteeForm(p => ({ ...p, email: e.target.value }))}
|
||||
placeholder="beispiel@firma.com"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={styles.formLabel}>Benutzername (optional)</label>
|
||||
<input
|
||||
className={styles.formInput}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={inviteeForm.username}
|
||||
onChange={e => setInviteeForm(p => ({ ...p, username: e.target.value }))}
|
||||
placeholder="z. B. vorname.nachname"
|
||||
/>
|
||||
<p style={{ fontSize: '11px', color: 'var(--text-secondary)', marginTop: '4px' }}>
|
||||
Der Benutzername wird vom eingeladenen Benutzer beim Annehmen der Einladung festgelegt.
|
||||
Mindestens eines der beiden Felder ausfüllen. Mit Benutzername muss der Eingeladene genau diesen Namen beim Annehmen verwenden.
|
||||
</p>
|
||||
</div>
|
||||
{roles.length > 0 && (
|
||||
|
|
@ -497,7 +516,7 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
display: 'flex', alignItems: 'center', gap: '6px',
|
||||
padding: '6px 12px', borderRadius: '6px',
|
||||
background: inviteeForm.roleIds.includes(r.id) ? '#dbeafe' : 'var(--bg-secondary, #f8fafc)',
|
||||
border: `1px solid ${inviteeForm.roleIds.includes(r.id) ? 'var(--primary-color, #3b82f6)' : 'var(--border-color, #e2e8f0)'}`,
|
||||
border: `1px solid ${inviteeForm.roleIds.includes(r.id) ? 'var(--primary-color, #F25843)' : 'var(--border-color, #e2e8f0)'}`,
|
||||
fontSize: '12px', cursor: 'pointer',
|
||||
}}>
|
||||
<input
|
||||
|
|
@ -519,7 +538,10 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
<button
|
||||
className={styles.primaryButton}
|
||||
onClick={addInviteeByEmail}
|
||||
disabled={!inviteeForm.email.trim() || (roles.length > 0 && inviteeForm.roleIds.length === 0)}
|
||||
disabled={
|
||||
(!inviteeForm.email.trim() && !inviteeForm.username.trim())
|
||||
|| (roles.length > 0 && inviteeForm.roleIds.length === 0)
|
||||
}
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
|
|
@ -552,7 +574,7 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
display: 'flex', alignItems: 'center', gap: '6px',
|
||||
padding: '6px 12px', borderRadius: '6px',
|
||||
background: inviteeForm.roleIds.includes(r.id) ? '#dbeafe' : 'var(--bg-secondary, #f8fafc)',
|
||||
border: `1px solid ${inviteeForm.roleIds.includes(r.id) ? 'var(--primary-color, #3b82f6)' : 'var(--border-color, #e2e8f0)'}`,
|
||||
border: `1px solid ${inviteeForm.roleIds.includes(r.id) ? 'var(--primary-color, #F25843)' : 'var(--border-color, #e2e8f0)'}`,
|
||||
fontSize: '12px', cursor: 'pointer',
|
||||
}}>
|
||||
<input
|
||||
|
|
@ -586,7 +608,7 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '13px' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid var(--border-color, #C5D9E8)' }}>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>E-Mail</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>E-Mail / Benutzer</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>Benutzername</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>Rollen</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>Typ</th>
|
||||
|
|
@ -596,14 +618,16 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
<tbody>
|
||||
{invitees.map((inv, idx) => (
|
||||
<tr key={idx} style={{ borderBottom: '1px solid var(--bg-secondary, #f1f5f9)' }}>
|
||||
<td style={{ padding: '8px' }}>{inv.email}</td>
|
||||
<td style={{ padding: '8px', color: 'var(--text-secondary)' }}>{inv.isExisting ? inv.username : ''}</td>
|
||||
<td style={{ padding: '8px' }}>{inv.email || '—'}</td>
|
||||
<td style={{ padding: '8px', color: 'var(--text-secondary)' }}>
|
||||
{inv.username || ''}
|
||||
</td>
|
||||
<td style={{ padding: '8px' }}>
|
||||
{inv.roleIds.length > 0
|
||||
? roles.filter(r => inv.roleIds.includes(r.id)).map(r => r.roleLabel).join(', ')
|
||||
: '-'}
|
||||
</td>
|
||||
<td style={{ padding: '8px', fontSize: '12px' }}>{inv.isExisting ? 'Bestehend' : 'Neu'}</td>
|
||||
<td style={{ padding: '8px', fontSize: '12px' }}>{inv.isExisting ? 'Bestehend' : 'Neu (Einladung)'}</td>
|
||||
<td style={{ padding: '8px', textAlign: 'right' }}>
|
||||
<button
|
||||
onClick={() => removeInvitee(idx)}
|
||||
|
|
@ -654,7 +678,8 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
<ul style={{ margin: '8px 0 0 20px', padding: 0 }}>
|
||||
{invitees.map((inv, i) => (
|
||||
<li key={i} style={{ marginBottom: '4px' }}>
|
||||
{inv.email}{inv.isExisting && inv.username ? ` (${inv.username})` : ''}
|
||||
{[inv.email || null, inv.username ? `@${inv.username}` : null].filter(Boolean).join(' · ')
|
||||
|| '—'}
|
||||
{inv.roleIds.length > 0 && ` – ${roles.filter(r => inv.roleIds.includes(r.id)).map(r => r.roleLabel).join(', ')}`}
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -680,7 +705,7 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '13px' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid var(--border-color, #C5D9E8)' }}>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>E-Mail</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>E-Mail / Benutzer</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>Status</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px' }}>E-Mail gesendet</th>
|
||||
</tr>
|
||||
|
|
@ -688,7 +713,11 @@ export const AdminInvitationWizardPage: React.FC = () => {
|
|||
<tbody>
|
||||
{dispatchResults.map((r, idx) => (
|
||||
<tr key={idx} style={{ borderBottom: '1px solid var(--bg-secondary, #f1f5f9)' }}>
|
||||
<td style={{ padding: '8px' }}>{r.email}{r.username ? ` (${r.username})` : ''}</td>
|
||||
<td style={{ padding: '8px' }}>
|
||||
{(r.email || '').trim() && r.username
|
||||
? `${(r.email || '').trim()} (@${r.username})`
|
||||
: (r.email || '').trim() || (r.username ? `@${r.username}` : '—')}
|
||||
</td>
|
||||
<td style={{ padding: '8px' }}>
|
||||
<span style={{
|
||||
padding: '2px 8px', borderRadius: '4px', fontSize: '12px',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ import { FaSync, FaPlug, FaGoogle, FaMicrosoft, FaLink, FaRedo, FaShieldAlt, FaT
|
|||
import { getApiBaseUrl } from '../../../config/config';
|
||||
import styles from '../admin/Admin.module.css';
|
||||
|
||||
/** Wenn false: keine neue ClickUp-Verbindung über diese Seite (Buttons inaktiv). */
|
||||
const isClickupConnectionUiEnabled = false;
|
||||
|
||||
export const ConnectionsPage: React.FC = () => {
|
||||
// Use the consolidated hook
|
||||
const {
|
||||
|
|
@ -190,8 +193,9 @@ export const ConnectionsPage: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Handle create ClickUp connection
|
||||
// Handle create ClickUp connection (UI kann per Flag abgeschaltet sein)
|
||||
const handleCreateClickup = async () => {
|
||||
if (!isClickupConnectionUiEnabled) return;
|
||||
try {
|
||||
await createClickupConnectionAndAuth();
|
||||
refetch();
|
||||
|
|
@ -245,7 +249,8 @@ export const ConnectionsPage: React.FC = () => {
|
|||
<div>
|
||||
<h1 className={styles.pageTitle}>Verbindungen</h1>
|
||||
<p className={styles.pageSubtitle}>
|
||||
Persönliche Datenanbindungen verwalten (OAuth: Google, Microsoft, ClickUp)
|
||||
Persönliche Datenanbindungen verwalten (OAuth: Google, Microsoft
|
||||
{isClickupConnectionUiEnabled ? ', ClickUp' : ''})
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.headerActions}>
|
||||
|
|
@ -280,14 +285,17 @@ export const ConnectionsPage: React.FC = () => {
|
|||
>
|
||||
<FaMicrosoft /> Microsoft
|
||||
</button>
|
||||
<button
|
||||
className={styles.clickupButton}
|
||||
onClick={handleCreateClickup}
|
||||
disabled={isConnecting}
|
||||
title="ClickUp-Konto verbinden"
|
||||
>
|
||||
<FaTasks /> ClickUp
|
||||
</button>
|
||||
{isClickupConnectionUiEnabled && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.clickupButton}
|
||||
onClick={handleCreateClickup}
|
||||
disabled={isConnecting}
|
||||
title="ClickUp-Konto verbinden"
|
||||
>
|
||||
<FaTasks /> ClickUp
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -304,7 +312,9 @@ export const ConnectionsPage: React.FC = () => {
|
|||
<FaPlug className={styles.emptyIcon} />
|
||||
<h3 className={styles.emptyTitle}>Keine Verbindungen vorhanden</h3>
|
||||
<p className={styles.emptyDescription}>
|
||||
Verbinden Sie Ihr Google-, Microsoft- oder ClickUp-Konto, um loszulegen.
|
||||
{isClickupConnectionUiEnabled
|
||||
? 'Verbinden Sie Ihr Google-, Microsoft- oder ClickUp-Konto, um loszulegen.'
|
||||
: 'Verbinden Sie Ihr Google- oder Microsoft-Konto, um loszulegen.'}
|
||||
</p>
|
||||
{canCreate && (
|
||||
<div style={{ display: 'flex', gap: '0.75rem', justifyContent: 'center', flexWrap: 'wrap' }}>
|
||||
|
|
@ -322,13 +332,16 @@ export const ConnectionsPage: React.FC = () => {
|
|||
>
|
||||
<FaMicrosoft /> Mit Microsoft verbinden
|
||||
</button>
|
||||
<button
|
||||
className={styles.clickupButton}
|
||||
onClick={handleCreateClickup}
|
||||
disabled={isConnecting}
|
||||
>
|
||||
<FaTasks /> Mit ClickUp verbinden
|
||||
</button>
|
||||
{isClickupConnectionUiEnabled && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.clickupButton}
|
||||
onClick={handleCreateClickup}
|
||||
disabled={isConnecting}
|
||||
>
|
||||
<FaTasks /> Mit ClickUp verbinden
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -362,7 +375,9 @@ export const ConnectionsPage: React.FC = () => {
|
|||
icon: <FaLink />,
|
||||
onClick: handleConnect,
|
||||
title: 'Verbinden',
|
||||
visible: (row: Connection) => row.status !== 'active',
|
||||
visible: (row: Connection) =>
|
||||
row.status !== 'active' &&
|
||||
(isClickupConnectionUiEnabled || row.authority !== 'clickup'),
|
||||
loading: () => isConnecting,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ export const FilesPage: React.FC = () => {
|
|||
style={{
|
||||
width: 6,
|
||||
cursor: 'col-resize',
|
||||
background: isDragging ? 'var(--color-primary, #1976d2)' : 'transparent',
|
||||
background: isDragging ? 'var(--primary-dark-bg, rgba(242, 88, 67, 0.2))' : 'transparent',
|
||||
transition: isDragging ? 'none' : 'background 0.15s',
|
||||
flexShrink: 0,
|
||||
zIndex: 10,
|
||||
|
|
|
|||
|
|
@ -676,7 +676,7 @@ export const BillingAdmin: React.FC = () => {
|
|||
padding: '8px 16px',
|
||||
textDecoration: 'none',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: isActive ? 'var(--color-primary, #3b82f6)' : 'transparent',
|
||||
backgroundColor: isActive ? 'var(--primary-color, #F25843)' : 'transparent',
|
||||
color: isActive ? 'white' : 'var(--color-text, #e0e0e0)',
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
cursor: 'pointer',
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ const TabNav: React.FC<TabNavProps> = ({ activeTab, onTabChange }) => {
|
|||
padding: '8px 16px',
|
||||
textDecoration: 'none',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: isActive ? 'var(--color-primary, #3b82f6)' : 'transparent',
|
||||
backgroundColor: isActive ? 'var(--primary-color, #F25843)' : 'transparent',
|
||||
color: isActive ? 'white' : 'var(--color-text, #e0e0e0)',
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
cursor: 'pointer',
|
||||
|
|
@ -646,7 +646,7 @@ export const BillingDataView: React.FC = () => {
|
|||
? 'var(--color-error, #ef4444)'
|
||||
: pct >= 70
|
||||
? 'var(--color-warning, #f59e0b)'
|
||||
: 'var(--primary-color, #3b82f6)';
|
||||
: 'var(--primary-color, #F25843)';
|
||||
return (
|
||||
<div key={sv.mandateId} className={styles.balanceCard}>
|
||||
<h3 className={styles.mandateName}>{sv.mandateName}</h3>
|
||||
|
|
@ -726,7 +726,7 @@ export const BillingDataView: React.FC = () => {
|
|||
? 'var(--color-error, #ef4444)'
|
||||
: pct >= 70
|
||||
? 'var(--color-warning, #f59e0b)'
|
||||
: 'var(--primary-color, #3b82f6)';
|
||||
: 'var(--primary-color, #F25843)';
|
||||
|
||||
return (
|
||||
<div key={sv.mandateId} style={{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const _statusLabel: Record<string, { label: string; color: string }> = {
|
|||
PENDING: { label: 'Zahlung ausstehend', color: '#f59e0b' },
|
||||
SCHEDULED: { label: 'Geplant', color: '#8b5cf6' },
|
||||
ACTIVE: { label: 'Aktiv', color: '#22c55e' },
|
||||
TRIALING: { label: 'Testphase', color: '#3b82f6' },
|
||||
TRIALING: { label: 'Testphase', color: '#38bdf8' },
|
||||
PAST_DUE: { label: 'Zahlung ausstehend', color: '#f59e0b' },
|
||||
EXPIRED: { label: 'Abgelaufen', color: '#6b7280' },
|
||||
};
|
||||
|
|
@ -75,13 +75,13 @@ const PlanCard: React.FC<PlanCardProps> = ({ plan, isCurrent, onActivate, activa
|
|||
|
||||
return (
|
||||
<div style={{
|
||||
border: isCurrent ? '2px solid var(--color-primary, #3b82f6)' : '1px solid var(--color-border, #333)',
|
||||
border: isCurrent ? '2px solid var(--primary-color, #F25843)' : '1px solid var(--color-border, var(--border-color, #333))',
|
||||
borderRadius: '8px',
|
||||
padding: '1.25rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.75rem',
|
||||
background: isCurrent ? 'rgba(59,130,246,0.06)' : 'var(--color-surface, #1a1a2e)',
|
||||
background: isCurrent ? 'var(--primary-dark-bg, rgba(242, 88, 67, 0.12))' : 'var(--surface-color, var(--bg-secondary, #1a1a2e))',
|
||||
minWidth: 220,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
|
|
@ -89,12 +89,12 @@ const PlanCard: React.FC<PlanCardProps> = ({ plan, isCurrent, onActivate, activa
|
|||
{isCurrent && (
|
||||
<span style={{
|
||||
fontSize: '0.7rem', padding: '2px 8px', borderRadius: '4px',
|
||||
background: 'var(--color-primary, #3b82f6)', color: '#fff', fontWeight: 600,
|
||||
background: 'var(--primary-color, #F25843)', color: '#fff', fontWeight: 600,
|
||||
}}>Aktuell</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #888)', margin: 0 }}>
|
||||
<p style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', margin: 0 }}>
|
||||
{_t(plan.description)}
|
||||
</p>
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ const PlanCard: React.FC<PlanCardProps> = ({ plan, isCurrent, onActivate, activa
|
|||
<div style={{ fontSize: '0.85rem' }}>
|
||||
<div>User: <strong>{_formatCurrency(plan.pricePerUserCHF)}</strong> / {_periodLabel[plan.billingPeriod] || plan.billingPeriod}</div>
|
||||
<div>Instanz: <strong>{_formatCurrency(plan.pricePerFeatureInstanceCHF)}</strong> / {_periodLabel[plan.billingPeriod] || plan.billingPeriod}</div>
|
||||
<div style={{ marginTop: '0.35rem', color: 'var(--text-secondary, #888)' }}>
|
||||
<div style={{ marginTop: '0.35rem', color: 'var(--text-secondary)' }}>
|
||||
AI-Budget (inkl.): <strong>{_formatCurrency(plan.budgetAiCHF ?? 0)}</strong> / Periode
|
||||
{' · '}
|
||||
Speicher (inkl.):{' '}
|
||||
|
|
@ -112,7 +112,7 @@ const PlanCard: React.FC<PlanCardProps> = ({ plan, isCurrent, onActivate, activa
|
|||
: formatBinaryDataSizeFromMebibytes(plan.maxDataVolumeMB)}
|
||||
</strong>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #888)', marginTop: '0.25rem' }}>
|
||||
<div style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', marginTop: '0.25rem' }}>
|
||||
Speicher über Plan: {_formatCurrency(storageOverageChfPerGbMonth)} / GB / Monat
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -140,7 +140,7 @@ const PlanCard: React.FC<PlanCardProps> = ({ plan, isCurrent, onActivate, activa
|
|||
disabled={!!activatingPlanKey}
|
||||
style={{
|
||||
marginTop: 'auto', padding: '8px 16px', borderRadius: '6px', border: 'none',
|
||||
background: 'var(--color-primary, #3b82f6)', color: '#fff', fontWeight: 600,
|
||||
background: 'var(--primary-color, #F25843)', color: '#fff', fontWeight: 600,
|
||||
cursor: activatingPlanKey ? 'wait' : 'pointer',
|
||||
opacity: activatingPlanKey ? 0.6 : 1,
|
||||
}}
|
||||
|
|
@ -177,15 +177,15 @@ const SubInfoCard: React.FC<SubInfoProps> = ({ sub, plan, label, onCancel, onRea
|
|||
|
||||
return (
|
||||
<div style={{
|
||||
border: '1px solid var(--color-border, #333)',
|
||||
border: '1px solid var(--color-border, var(--border-color, #333))',
|
||||
borderRadius: '8px',
|
||||
padding: '1.25rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.5rem',
|
||||
background: 'var(--color-surface, #1a1a2e)',
|
||||
background: 'var(--surface-color, var(--bg-secondary, #1a1a2e))',
|
||||
}}>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--text-secondary, #888)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
||||
{label}
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
|
|
@ -229,7 +229,7 @@ const SubInfoCard: React.FC<SubInfoProps> = ({ sub, plan, label, onCancel, onRea
|
|||
|
||||
{!isPending && !isScheduled && (
|
||||
<div style={{
|
||||
fontSize: '0.85rem', color: 'var(--text-secondary, #888)',
|
||||
fontSize: '0.85rem', color: 'var(--text-secondary)',
|
||||
display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.25rem 1rem',
|
||||
}}>
|
||||
<span>Gestartet: {_formatDate(sub.startedAt)}</span>
|
||||
|
|
@ -263,7 +263,7 @@ const SubInfoCard: React.FC<SubInfoProps> = ({ sub, plan, label, onCancel, onRea
|
|||
disabled={reactivating}
|
||||
style={{
|
||||
padding: '6px 14px', borderRadius: '6px', border: 'none',
|
||||
background: 'var(--color-primary, #3b82f6)', color: '#fff',
|
||||
background: 'var(--primary-color, #F25843)', color: '#fff',
|
||||
fontWeight: 600, cursor: reactivating ? 'wait' : 'pointer', fontSize: '0.85rem',
|
||||
}}
|
||||
>
|
||||
|
|
@ -443,9 +443,9 @@ export const SubscriptionTab: React.FC<SubscriptionTabProps> = ({ mandateId }) =
|
|||
{checkoutMessage && (
|
||||
<div style={{
|
||||
marginBottom: '1rem', padding: '0.75rem 1rem', borderRadius: '6px',
|
||||
background: checkoutMessage.type === 'success' ? 'rgba(34,197,94,0.12)' : 'rgba(59,130,246,0.12)',
|
||||
border: `1px solid ${checkoutMessage.type === 'success' ? '#22c55e' : '#3b82f6'}`,
|
||||
color: checkoutMessage.type === 'success' ? '#22c55e' : '#3b82f6',
|
||||
background: checkoutMessage.type === 'success' ? 'rgba(34,197,94,0.12)' : 'var(--primary-dark-bg, rgba(242, 88, 67, 0.12))',
|
||||
border: `1px solid ${checkoutMessage.type === 'success' ? '#22c55e' : 'var(--primary-color, #F25843)'}`,
|
||||
color: checkoutMessage.type === 'success' ? '#22c55e' : 'var(--primary-light, #F25843)',
|
||||
fontSize: '0.9rem',
|
||||
}}>
|
||||
{checkoutMessage.text}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ export const ChatStream: React.FC<ChatStreamProps> = ({
|
|||
</td>
|
||||
),
|
||||
a: ({ href, children }) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" style={{ color: '#1976d2' }}>
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--primary-color, #F25843)' }}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
|
|
@ -222,7 +222,7 @@ export const ChatStream: React.FC<ChatStreamProps> = ({
|
|||
onClick={onOpenEditor}
|
||||
style={{
|
||||
padding: '5px 14px', borderRadius: 4, border: 'none',
|
||||
background: 'var(--primary-color, #1976d2)', color: '#fff',
|
||||
background: 'var(--primary-color, #F25843)', color: '#fff',
|
||||
cursor: 'pointer', fontSize: 12, fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
|
|
@ -280,7 +280,7 @@ export const ChatStream: React.FC<ChatStreamProps> = ({
|
|||
}}>
|
||||
<span className="workspace-spinner" style={{
|
||||
display: 'inline-block', width: 12, height: 12,
|
||||
border: '2px solid #ccc', borderTopColor: '#1976d2',
|
||||
border: '2px solid var(--border-color, #ccc)', borderTopColor: 'var(--primary-color, #F25843)',
|
||||
borderRadius: '50%', animation: 'workspace-spin 0.8s linear infinite',
|
||||
}} />
|
||||
Processing...
|
||||
|
|
@ -373,7 +373,7 @@ function _FileCard({ doc }: { doc: MessageDocument }) {
|
|||
{ext.toUpperCase()}{sizeLabel ? ` \u00b7 ${sizeLabel}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<span style={{ fontSize: 14, color: '#1976d2' }} title="Download">⬇</span>
|
||||
<span style={{ fontSize: 14, color: 'var(--primary-color, #F25843)' }} title="Download">⬇</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -455,7 +455,7 @@ function _AudioPlayer({ url, language }: { url: string; language?: string; charC
|
|||
onClick={_togglePlay}
|
||||
style={{
|
||||
width: 32, height: 32, borderRadius: '50%', border: 'none',
|
||||
background: 'var(--primary-color, #1976d2)', color: '#fff',
|
||||
background: 'var(--primary-color, #F25843)', color: '#fff',
|
||||
cursor: 'pointer', fontSize: 14,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
|
|
@ -473,7 +473,7 @@ function _AudioPlayer({ url, language }: { url: string; language?: string; charC
|
|||
}}>
|
||||
<div style={{
|
||||
height: '100%', borderRadius: 2,
|
||||
background: 'var(--primary-color, #1976d2)',
|
||||
background: 'var(--primary-color, #F25843)',
|
||||
width: `${progress * 100}%`,
|
||||
transition: 'width 0.2s',
|
||||
}} />
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ const _EditorTab: React.FC<{
|
|||
padding: '6px 16px',
|
||||
fontSize: 13,
|
||||
border: 'none',
|
||||
borderBottom: isActive ? '2px solid var(--primary-color, #1976d2)' : '2px solid transparent',
|
||||
borderBottom: isActive ? '2px solid var(--primary-color, #F25843)' : '2px solid transparent',
|
||||
background: isActive ? 'var(--bg-primary, #fff)' : 'transparent',
|
||||
cursor: 'pointer',
|
||||
whiteSpace: 'nowrap',
|
||||
|
|
|
|||
|
|
@ -324,8 +324,8 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
|
|||
borderTop: '1px solid var(--border-color, #e0e0e0)',
|
||||
position: 'relative',
|
||||
flexShrink: 0,
|
||||
outline: treeDropOver ? '2px dashed #1976d2' : 'none',
|
||||
background: treeDropOver ? 'rgba(25, 118, 210, 0.04)' : undefined,
|
||||
outline: treeDropOver ? '2px dashed var(--primary-color, #F25843)' : 'none',
|
||||
background: treeDropOver ? 'var(--primary-dark-bg, rgba(242, 88, 67, 0.08))' : undefined,
|
||||
transition: 'background 0.15s, outline 0.15s',
|
||||
}}
|
||||
onDragOver={_handlePromptDragOver}
|
||||
|
|
@ -534,7 +534,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
|
|||
style={{
|
||||
width: _controlSize, height: _controlSize, borderRadius: 8, border: '1px solid var(--border-color, #ddd)',
|
||||
background: 'var(--secondary-bg, #f5f5f5)',
|
||||
color: uploading ? '#1976d2' : '#666',
|
||||
color: uploading ? 'var(--primary-color, #F25843)' : 'var(--text-secondary, #666)',
|
||||
cursor: uploading || isProcessing ? 'not-allowed' : 'pointer',
|
||||
fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
opacity: isProcessing ? 0.5 : 1,
|
||||
|
|
@ -706,10 +706,10 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
|
|||
}}
|
||||
style={{
|
||||
padding: '8px 12px', cursor: 'pointer', fontSize: 13,
|
||||
background: lang.code === voiceLanguage ? 'var(--primary-color, #1976d2)' : 'transparent',
|
||||
background: lang.code === voiceLanguage ? 'var(--primary-color, #F25843)' : 'transparent',
|
||||
color: lang.code === voiceLanguage ? '#fff' : 'var(--text-primary, #333)',
|
||||
}}
|
||||
onMouseEnter={e => { if (lang.code !== voiceLanguage) e.currentTarget.style.background = '#f5f5f5'; }}
|
||||
onMouseEnter={e => { if (lang.code !== voiceLanguage) e.currentTarget.style.background = 'var(--bg-secondary, #f5f5f5)'; }}
|
||||
onMouseLeave={e => { if (lang.code !== voiceLanguage) e.currentTarget.style.background = ''; }}
|
||||
>
|
||||
{lang.label} ({lang.code})
|
||||
|
|
@ -751,7 +751,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
|
|||
disabled={!prompt.trim()}
|
||||
style={{
|
||||
padding: '10px 20px', borderRadius: 8, border: 'none',
|
||||
background: prompt.trim() ? 'var(--primary-color, #1976d2)' : '#ccc',
|
||||
background: prompt.trim() ? 'var(--primary-color, #F25843)' : 'var(--color-gray-disabled, #ccc)',
|
||||
color: '#fff', cursor: prompt.trim() ? 'pointer' : 'default', fontWeight: 600,
|
||||
minWidth: isMobile ? 84 : undefined,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -220,12 +220,12 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
flex: 1,
|
||||
padding: '6px 0',
|
||||
border: 'none',
|
||||
borderBottom: active ? '2px solid var(--primary-color, #1976d2)' : '2px solid transparent',
|
||||
borderBottom: active ? '2px solid var(--primary-color, #F25843)' : '2px solid transparent',
|
||||
background: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: 11,
|
||||
fontWeight: active ? 600 : 400,
|
||||
color: active ? 'var(--primary-color, #1976d2)' : '#888',
|
||||
color: active ? 'var(--primary-color, #F25843)' : 'var(--text-tertiary, #888)',
|
||||
textTransform: 'uppercase' as const,
|
||||
});
|
||||
|
||||
|
|
@ -326,7 +326,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
<div
|
||||
onMouseDown={e => _leftResize.onMouseDown(e, 1)}
|
||||
style={{ width: 4, cursor: 'col-resize', background: 'transparent', flexShrink: 0 }}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = '#1976d2')}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = 'var(--primary-color, #F25843)')}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -377,7 +377,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
padding: '6px 10px',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border-color, #ddd)',
|
||||
background: rightTab === 'activity' ? '#e8f3ff' : '#f7f7f7',
|
||||
background: rightTab === 'activity' ? 'var(--primary-dark-bg, rgba(242, 88, 67, 0.1))' : '#f7f7f7',
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
}}
|
||||
|
|
@ -390,7 +390,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
padding: '6px 10px',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border-color, #ddd)',
|
||||
background: rightTab === 'preview' ? '#e8f3ff' : '#f7f7f7',
|
||||
background: rightTab === 'preview' ? 'var(--primary-dark-bg, rgba(242, 88, 67, 0.1))' : '#f7f7f7',
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
}}
|
||||
|
|
@ -402,10 +402,10 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
{isDragOver && (
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0, zIndex: 100,
|
||||
background: 'rgba(25, 118, 210, 0.08)',
|
||||
border: '2px dashed #1976d2', borderRadius: 8,
|
||||
background: 'var(--primary-dark-bg, rgba(242, 88, 67, 0.1))',
|
||||
border: '2px dashed var(--primary-color, #F25843)', borderRadius: 8,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 16, fontWeight: 600, color: '#1976d2',
|
||||
fontSize: 16, fontWeight: 600, color: 'var(--primary-color, #F25843)',
|
||||
pointerEvents: 'none',
|
||||
}}>
|
||||
Dateien hier ablegen
|
||||
|
|
@ -452,7 +452,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
<div
|
||||
onMouseDown={e => _rightResize.onMouseDown(e, -1)}
|
||||
style={{ width: 4, cursor: 'col-resize', background: 'transparent', flexShrink: 0 }}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = '#1976d2')}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = 'var(--primary-color, #F25843)')}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -46,14 +46,14 @@ export const WorkspaceSettingsPage: React.FC = () => {
|
|||
padding: '10px 20px',
|
||||
border: 'none',
|
||||
borderBottom: activeTab === tab.key
|
||||
? '2px solid var(--primary-color, #1976d2)'
|
||||
? '2px solid var(--primary-color, #F25843)'
|
||||
: '2px solid transparent',
|
||||
background: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: 14,
|
||||
fontWeight: activeTab === tab.key ? 600 : 400,
|
||||
color: activeTab === tab.key
|
||||
? 'var(--primary-color, #1976d2)'
|
||||
? 'var(--primary-color, #F25843)'
|
||||
: 'var(--text-secondary, #888)',
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,15 @@
|
|||
|
||||
/* Error color */
|
||||
--error-color: #dc2626;
|
||||
|
||||
/* Legacy / inline-style aliases (override :root beige --color-primary) */
|
||||
--color-primary: var(--primary-color);
|
||||
--color-primary-hover: var(--primary-color-dark);
|
||||
--color-primary-disabled: var(--primary-color-light);
|
||||
--color-border: var(--border-color);
|
||||
--bg-card: var(--bg-primary);
|
||||
--bg-input: #ffffff;
|
||||
--bg-hover: var(--hover-bg);
|
||||
}
|
||||
|
||||
/* ============================================== */
|
||||
|
|
@ -92,9 +101,9 @@
|
|||
--color-surface: #1E1D1A;
|
||||
--color-text: #E5E7EB;
|
||||
|
||||
--color-primary: #C7C5B2;
|
||||
--color-primary-hover: #E0DECC;
|
||||
--color-primary-disabled: #59584F;
|
||||
--color-primary: var(--primary-color);
|
||||
--color-primary-hover: var(--primary-color-dark);
|
||||
--color-primary-disabled: rgba(242, 88, 67, 0.35);
|
||||
|
||||
--color-secondary: #F25843;
|
||||
--color-secondary-hover: #FF715C;
|
||||
|
|
@ -108,9 +117,10 @@
|
|||
--color-secondary-red-hover: #E17683;
|
||||
--color-secondary-red-disabled: #70363C;
|
||||
|
||||
--color-gray: #181818;
|
||||
--color-gray-hover: #2E2E2E;
|
||||
--color-gray-disabled: #505050;
|
||||
/* Readable neutrals on dark (was #181818 — same as bg, illegible) */
|
||||
--color-gray: #9ca3af;
|
||||
--color-gray-hover: #d1d5db;
|
||||
--color-gray-disabled: #57534e;
|
||||
|
||||
/* Background colors */
|
||||
--bg-primary: #181818;
|
||||
|
|
@ -146,4 +156,9 @@
|
|||
|
||||
/* Error color */
|
||||
--error-color: #ef4444;
|
||||
|
||||
--color-border: var(--border-color);
|
||||
--bg-card: #252422;
|
||||
--bg-input: #2c2926;
|
||||
--bg-hover: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue