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