Portal Editor admin: replace glyph icons with SVGs (#6999)

# Description of Changes

Swaps the emoji and box glyphs in the editor deployment, pairing and
storage panels for tinted icons consistent with the rest of the portal.

Part of a portal (processor) UI-consistency pass, split into small
focused PRs.


---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [x] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
This commit is contained in:
Anthony Stirling
2026-07-16 14:01:11 +00:00
committed by GitHub
parent 1835e6fe6b
commit 275d70242e
5 changed files with 100 additions and 50 deletions
@@ -119,14 +119,13 @@ export interface EditorDeploymentResponse {
/* ──────────────────────────────────────────────────────────────────────── */
export interface TargetMeta {
icon: string;
tone: "neutral" | "blue" | "purple";
}
export const TARGET_META: Record<TargetKind, TargetMeta> = {
cloud: { icon: "☁", tone: "blue" },
docker: { icon: "▣", tone: "neutral" },
kubernetes: { icon: "⎈", tone: "purple" },
cloud: { tone: "blue" },
docker: { tone: "neutral" },
kubernetes: { tone: "purple" },
};
export const INSTANCE_STATUS_TONE: Record<
@@ -1,8 +1,21 @@
import { useTranslation } from "react-i18next";
import CloudQueueRounded from "@mui/icons-material/CloudQueueRounded";
import HubRounded from "@mui/icons-material/HubRounded";
import Inventory2Rounded from "@mui/icons-material/Inventory2Rounded";
import { Button, Card, CodeBlock, StatusBadge } from "@app/ui";
import { TARGET_META, type DeploymentTarget } from "@portal/api/editorDeploy";
import {
TARGET_META,
type DeploymentTarget,
type TargetKind,
} from "@portal/api/editorDeploy";
import { useTier } from "@portal/contexts/TierContext";
const TARGET_ICON: Record<TargetKind, typeof CloudQueueRounded> = {
cloud: CloudQueueRounded,
docker: Inventory2Rounded,
kubernetes: HubRounded,
};
const STATE_BADGE_TONE: Record<
DeploymentTarget["state"],
"success" | "info" | "neutral"
@@ -36,6 +49,7 @@ export function DeploymentTargets({ targets, onUpgrade }: Props) {
<div className="portal-editor__targets">
{targets.map((target) => {
const meta = TARGET_META[target.kind];
const Icon = TARGET_ICON[target.kind];
const locked = target.state === "locked";
return (
<Card
@@ -48,7 +62,7 @@ export function DeploymentTargets({ targets, onUpgrade }: Props) {
className={`portal-editor__target-icon portal-editor__target-icon--${meta.tone}`}
aria-hidden
>
{meta.icon}
<Icon style={{ fontSize: "1.2rem" }} />
</span>
<div className="portal-editor__target-titles">
<h3 className="portal-editor__target-name">{target.label}</h3>
@@ -1,12 +1,21 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import CastRounded from "@mui/icons-material/CastRounded";
import TerminalRounded from "@mui/icons-material/TerminalRounded";
import VpnKeyRounded from "@mui/icons-material/VpnKeyRounded";
import { Button, Card, Chip, CodeBlock } from "@app/ui";
import type { PairingMethod, PairingOption } from "@portal/api/editorDeploy";
const METHOD_ICON: Record<PairingMethod, string> = {
token: "🔑",
shortcode: "📺",
iac: "🧱",
const METHOD_ICON: Record<PairingMethod, typeof VpnKeyRounded> = {
token: VpnKeyRounded,
shortcode: CastRounded,
iac: TerminalRounded,
};
const METHOD_TONE: Record<PairingMethod, "blue" | "purple" | "green"> = {
token: "blue",
shortcode: "purple",
iac: "green",
};
interface Props {
@@ -36,6 +45,7 @@ export function PairingPanel({ pairings, onUpgrade }: Props) {
<div className="portal-editor__pairings">
{pairings.map((p) => {
const isCode = p.method === "iac";
const Icon = METHOD_ICON[p.method];
return (
<Card
key={p.method}
@@ -43,8 +53,11 @@ export function PairingPanel({ pairings, onUpgrade }: Props) {
className="portal-editor__pairing"
>
<div className="portal-editor__pairing-head">
<span className="portal-editor__pairing-icon" aria-hidden>
{METHOD_ICON[p.method]}
<span
className={`portal-editor__pairing-icon portal-editor__pairing-icon--${METHOD_TONE[p.method]}`}
aria-hidden
>
<Icon style={{ fontSize: "1.2rem" }} />
</span>
<div className="portal-editor__pairing-titles">
<h3 className="portal-editor__pairing-name">{p.label}</h3>
@@ -1,5 +1,8 @@
import { useState } from "react";
import { useState, type ComponentType, type CSSProperties } from "react";
import { useTranslation } from "react-i18next";
import ArrowForwardRounded from "@mui/icons-material/ArrowForwardRounded";
import CloudRounded from "@mui/icons-material/CloudRounded";
import StorageRounded from "@mui/icons-material/StorageRounded";
import {
Button,
Card,
@@ -20,13 +23,13 @@ import {
import { SectionHeader } from "@portal/components/infrastructure/SectionHeader";
import { pct } from "@portal/components/infrastructure/infraFormat";
const PROVIDER_GLYPH: Record<
const PROVIDER_ICON: Record<
StorageConfig["providers"][number]["kind"],
string
ComponentType<{ style?: CSSProperties }>
> = {
stirling: "◆",
s3: "▣",
azure: "▤",
stirling: StorageRounded,
s3: CloudRounded,
azure: CloudRounded,
};
/** Storage fills past this fraction of quota are surfaced in red. */
@@ -143,35 +146,38 @@ export function StorageTab() {
sub={t("portal.infrastructure.storage.providers.subheading")}
/>
<ul className="portal-infra__providers">
{data.providers.map((p) => (
<li key={p.id} className="portal-infra__provider">
<span className="portal-infra__provider-glyph" aria-hidden>
{PROVIDER_GLYPH[p.kind]}
</span>
<span className="portal-infra__provider-text">
<span className="portal-infra__cell-strong">{p.name}</span>
<span className="portal-infra__muted">{p.detail}</span>
</span>
{p.connected ? (
<span className="portal-infra__provider-meta">
<span className="portal-infra__mono">
{t("portal.infrastructure.storage.gbValue", {
value: p.usedGb,
})}
</span>
<StatusBadge tone="success" size="sm">
{t("portal.infrastructure.storage.providers.connected")}
</StatusBadge>
{data.providers.map((p) => {
const ProviderIcon = PROVIDER_ICON[p.kind];
return (
<li key={p.id} className="portal-infra__provider">
<span className="portal-infra__provider-glyph" aria-hidden>
<ProviderIcon style={{ fontSize: "1.2rem" }} />
</span>
) : (
// TODO(backend): launch the provider OAuth/credential flow,
// then POST /v1/infrastructure/storage/providers/{id}/connect
<Button variant="secondary" size="sm">
{t("portal.infrastructure.storage.providers.connect")}
</Button>
)}
</li>
))}
<span className="portal-infra__provider-text">
<span className="portal-infra__cell-strong">{p.name}</span>
<span className="portal-infra__muted">{p.detail}</span>
</span>
{p.connected ? (
<span className="portal-infra__provider-meta">
<span className="portal-infra__mono">
{t("portal.infrastructure.storage.gbValue", {
value: p.usedGb,
})}
</span>
<StatusBadge tone="success" size="sm">
{t("portal.infrastructure.storage.providers.connected")}
</StatusBadge>
</span>
) : (
// TODO(backend): launch the provider OAuth/credential flow,
// then POST /v1/infrastructure/storage/providers/{id}/connect
<Button variant="secondary" size="sm">
{t("portal.infrastructure.storage.providers.connect")}
</Button>
)}
</li>
);
})}
</ul>
</Card>
@@ -205,7 +211,7 @@ export function StorageTab() {
</span>
</div>
<span className="portal-infra__lifecycle-arrow" aria-hidden>
<ArrowForwardRounded style={{ fontSize: "1.2rem" }} />
</span>
<div className="portal-infra__lifecycle-stage">
<span className="portal-infra__lifecycle-dot" />
@@ -217,7 +223,7 @@ export function StorageTab() {
</span>
</div>
<span className="portal-infra__lifecycle-arrow" aria-hidden>
<ArrowForwardRounded style={{ fontSize: "1.2rem" }} />
</span>
<div className="portal-infra__lifecycle-stage">
<span className="portal-infra__lifecycle-dot" />
@@ -181,8 +181,26 @@
}
.portal-editor__pairing-icon {
font-size: 1.125rem;
line-height: 1.4;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
flex-shrink: 0;
border-radius: var(--radius-md);
}
.portal-editor__pairing-icon--blue {
background: var(--color-blue-light);
color: var(--color-blue);
}
.portal-editor__pairing-icon--purple {
background: var(--color-purple-light);
color: var(--color-purple);
}
.portal-editor__pairing-icon--green {
background: var(--color-green-light);
color: var(--color-green);
}
.portal-editor__pairing-titles {