diff --git a/server/control-plane-store.mjs b/server/control-plane-store.mjs index f800c5b..480ac13 100644 --- a/server/control-plane-store.mjs +++ b/server/control-plane-store.mjs @@ -105,6 +105,7 @@ export function createControlPlaneStore({ projectRoot }) { demoEndsAt: nullableString(payload?.demoEndsAt), contactName: nullableString(payload?.contactName), contactEmail: nullableString(payload?.contactEmail), + avatarUrl: nullableString(payload?.avatarUrl), integrations: normalizeClientIntegrations(payload?.integrations), notes: nullableString(payload?.notes), createdAt: now, @@ -141,6 +142,7 @@ export function createControlPlaneStore({ projectRoot }) { client.demoEndsAt = nullableStringWithFallback(payload?.demoEndsAt, client.demoEndsAt ?? null); client.contactName = nullableStringWithFallback(payload?.contactName, client.contactName ?? null); client.contactEmail = nullableStringWithFallback(payload?.contactEmail, client.contactEmail ?? null); + client.avatarUrl = nullableStringWithFallback(payload?.avatarUrl, client.avatarUrl ?? null); if ("integrations" in (payload ?? {})) { client.integrations = normalizeClientIntegrations(payload.integrations, client.integrations); } diff --git a/src/entities/client/types.ts b/src/entities/client/types.ts index 405619b..4137181 100644 --- a/src/entities/client/types.ts +++ b/src/entities/client/types.ts @@ -20,6 +20,7 @@ export interface Client { demoEndsAt?: string | null; contactName?: string | null; contactEmail?: string | null; + avatarUrl?: string | null; integrations?: { taskManager?: { workspaceSlug?: string | null; diff --git a/src/shared/api/mockData.ts b/src/shared/api/mockData.ts index a9acc14..5598e6b 100644 --- a/src/shared/api/mockData.ts +++ b/src/shared/api/mockData.ts @@ -21,6 +21,7 @@ export const mockClients: Client[] = [ demoEndsAt: null, contactName: "DC Touch", contactEmail: "dcctouch@gmail.com", + avatarUrl: null, notes: "Live-клиент NODE.DC для первичной проверки control-plane, SSO и доступа к сервисам.", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, diff --git a/src/styles/globals.css b/src/styles/globals.css index c0d0593..43de421 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -449,6 +449,13 @@ code { object-fit: contain; } +.nodedc-expanded-workspace-avatar { + width: 72%; + height: 72%; + border-radius: inherit; + object-fit: contain; +} + .nodedc-expanded-user-group { display: inline-flex; height: var(--nodedc-shell-pill-height); @@ -1429,6 +1436,7 @@ code { } .admin-panel-content { + position: relative; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 1rem; @@ -2664,8 +2672,8 @@ code { } .admin-icon-action svg { - width: 80%; - height: 80%; + width: 0.78rem; + height: 0.78rem; } .invite-icon-action { @@ -2857,6 +2865,11 @@ code { -webkit-backdrop-filter: blur(34px) saturate(1.12); } +.client-editor-modal { + width: 100%; + max-height: 100%; +} + .service-content-modal__head, .service-content-modal__foot { display: flex; @@ -3021,6 +3034,80 @@ code { color: rgba(8, 8, 10, 0.96); } +.client-avatar-field { + align-self: stretch; +} + +.client-avatar-control { + display: grid; + min-height: 4rem; + grid-template-columns: 3.25rem minmax(0, 1fr) auto auto; + align-items: center; + gap: 0.75rem; + overflow: hidden; + border-radius: var(--launcher-radius-control); + background: rgba(255, 255, 255, 0.06); + padding: 0.38rem; +} + +.client-avatar-preview { + display: grid; + width: 3.1rem; + height: 3.1rem; + place-items: center; + overflow: hidden; + border-radius: var(--launcher-radius-circle); + background: rgba(255, 255, 255, 0.055); +} + +.client-avatar-preview img { + width: 72%; + height: 72%; + object-fit: contain; +} + +.client-avatar-control__copy { + display: grid; + min-width: 0; + gap: 0.18rem; +} + +.client-avatar-control__copy strong { + overflow: hidden; + color: var(--text-primary); + font-size: 0.84rem; + font-weight: 850; + text-overflow: ellipsis; + white-space: nowrap; +} + +.client-avatar-control__copy small, +.client-avatar-error { + color: var(--text-muted); + font-size: 0.74rem; + font-weight: 650; +} + +.client-avatar-upload-button { + position: relative; + min-height: 2.65rem; +} + +.client-avatar-upload-button input { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; +} + +.client-avatar-clear-action { + width: 2.35rem; + min-width: 2.35rem; + height: 2.35rem; +} + .service-content-field textarea { resize: vertical; line-height: 1.45; diff --git a/src/widgets/admin-overlay/AdminOverlay.tsx b/src/widgets/admin-overlay/AdminOverlay.tsx index 1d62205..f53f950 100644 --- a/src/widgets/admin-overlay/AdminOverlay.tsx +++ b/src/widgets/admin-overlay/AdminOverlay.tsx @@ -572,11 +572,11 @@ function ClientsSection({