diff --git a/backend/app/seed_demo.py b/backend/app/seed_demo.py
index b7d27ec..47c42da 100644
--- a/backend/app/seed_demo.py
+++ b/backend/app/seed_demo.py
@@ -52,7 +52,21 @@
DEMO_PASSWORD = "password"
DEMO_ORG_NAME = "EchoEd Demo School"
DEMO_COURSE_TITLE = "Introduction to Africa"
-DEMO_BACKEND_PUBLIC_URL = os.getenv("BACKEND_PUBLIC_URL", "http://127.0.0.1:8000").rstrip("/")
+
+
+def _demo_backend_public_url() -> str:
+ explicit_url = os.getenv("BACKEND_PUBLIC_URL")
+ if explicit_url:
+ return explicit_url.rstrip("/")
+
+ railway_domain = os.getenv("RAILWAY_PUBLIC_DOMAIN")
+ if railway_domain:
+ return f"https://{railway_domain.strip().rstrip('/')}"
+
+ return "http://127.0.0.1:8000"
+
+
+DEMO_BACKEND_PUBLIC_URL = _demo_backend_public_url()
DEMO_STORYBOOK_BASE_URL = f"{DEMO_BACKEND_PUBLIC_URL}/storybook"
DEMO_COLORINGS_BASE_URL = f"{DEMO_BACKEND_PUBLIC_URL}/colorings"
DEMO_MAP_REFERENCE_URL = "/assets/maps/africa-reference.svg"
diff --git a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.html b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.html
index efa5ee4..9839698 100644
--- a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.html
+++ b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.html
@@ -19,11 +19,7 @@
[attr.aria-expanded]="!collapsed"
aria-controls="echo-primary-navigation"
>
-
+
@@ -44,7 +40,7 @@
[attr.title]="collapsed ? item.label : null"
>
diff --git a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.scss b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.scss
index 140d762..9ea311e 100644
--- a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.scss
+++ b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.scss
@@ -137,11 +137,29 @@
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.1);
border-radius: 12px;
+ color: var(--ee-text-soft, #334155);
display: inline-flex;
height: 2rem;
justify-content: center;
width: 2rem;
flex: 0 0 auto;
+ transition:
+ background var(--ee-duration-base, 180ms) var(--ee-easing-standard, ease),
+ border-color var(--ee-duration-base, 180ms) var(--ee-easing-standard, ease),
+ color var(--ee-duration-base, 180ms) var(--ee-easing-standard, ease);
+}
+
+.sidebar-link:hover .sidebar-link__icon,
+.sidebar-link:focus-visible .sidebar-link__icon {
+ background: #ffffff;
+ border-color: rgba(15, 118, 110, 0.22);
+ color: var(--echo-primary, #0f766e);
+}
+
+.sidebar-link--active .sidebar-link__icon {
+ background: rgba(15, 118, 110, 0.12);
+ border-color: rgba(15, 118, 110, 0.3);
+ color: var(--echo-primary, #0f766e);
}
.sidebar-link__label {
diff --git a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.spec.ts b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.spec.ts
index 81e397c..423a3ad 100644
--- a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.spec.ts
+++ b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.spec.ts
@@ -76,9 +76,14 @@ describe('SidebarComponent', () => {
fixture.detectChanges();
const firstLink = fixture.nativeElement.querySelector('a') as HTMLAnchorElement;
+ const icon = firstLink.querySelector('app-icon');
+ const svg = firstLink.querySelector('svg');
expect(firstLink.getAttribute('aria-label')).toBe('Workspace');
expect(firstLink.getAttribute('title')).toBe('Workspace');
- expect(firstLink.textContent?.trim()).toBe('WS');
+ expect(firstLink.textContent?.trim()).toBe('');
+ expect(icon).not.toBeNull();
+ expect(svg).not.toBeNull();
+ expect(svg?.querySelectorAll('path').length).toBeGreaterThan(0);
});
it('keeps important sidebar controls labeled for accessibility', () => {
diff --git a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.ts b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.ts
index e1be8db..9f9a03e 100644
--- a/frontend/src/app/components/echo-sidebar/echo-sidebar.component.ts
+++ b/frontend/src/app/components/echo-sidebar/echo-sidebar.component.ts
@@ -80,23 +80,4 @@ export class SidebarComponent {
)
);
- iconGlyph(item: NavItem): string {
- const explicit: Record = {
- Workspace: 'WS',
- Projects: 'PJ',
- 'Product Studio': 'PS',
- Products: 'PD',
- 'Knowledge Sources': 'KS',
- Artifacts: 'AR',
- 'Review Center': 'RC',
- Access: 'AC',
- Analytics: 'AN',
- Community: 'CO',
- Learners: 'LR',
- Cohorts: 'CH',
- Settings: 'ST',
- };
- return explicit[item.label] || item.label.slice(0, 2).toUpperCase();
- }
-
}
diff --git a/frontend/src/app/shared/icon/icon.module.ts b/frontend/src/app/shared/icon/icon.module.ts
index 9569321..e926fef 100644
--- a/frontend/src/app/shared/icon/icon.module.ts
+++ b/frontend/src/app/shared/icon/icon.module.ts
@@ -1,7 +1,96 @@
import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Component, Input } from '@angular/core';
+
+type IconPathMap = Record;
+
+const ICON_PATHS: IconPathMap = {
+ Award: [
+ 'M12 15.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11Z',
+ 'M8.5 14.5 7 22l5-3 5 3-1.5-7.5',
+ ],
+ Book: [
+ 'M4 5.5A2.5 2.5 0 0 1 6.5 3H20v16H6.5A2.5 2.5 0 0 0 4 21.5v-16Z',
+ 'M4 5.5A2.5 2.5 0 0 1 6.5 8H20',
+ ],
+ BookOpen: [
+ 'M4 5.5A2.5 2.5 0 0 1 6.5 3H11v17H6.5A2.5 2.5 0 0 0 4 22V5.5Z',
+ 'M20 5.5A2.5 2.5 0 0 0 17.5 3H13v17h4.5A2.5 2.5 0 0 1 20 22V5.5Z',
+ ],
+ ClipboardList: [
+ 'M9 4h6a2 2 0 0 1 2 2v1H7V6a2 2 0 0 1 2-2Z',
+ 'M8 6H6a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-2',
+ 'M8 12h8M8 16h8',
+ ],
+ FileText: [
+ 'M6 3h8l4 4v14H6V3Z',
+ 'M14 3v5h5',
+ 'M8 13h8M8 17h6',
+ ],
+ Home: [
+ 'M3 11.5 12 4l9 7.5',
+ 'M5 10.5V21h5v-6h4v6h5V10.5',
+ ],
+ Menu: [
+ 'M4 6h16',
+ 'M4 12h16',
+ 'M4 18h16',
+ ],
+ Settings: [
+ 'M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z',
+ 'M19.4 15a1.7 1.7 0 0 0 .34 1.87l.04.04a2 2 0 0 1-2.83 2.83l-.04-.04a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.04 1.56V21a2 2 0 0 1-4 0v-.06a1.7 1.7 0 0 0-1.04-1.56 1.7 1.7 0 0 0-1.87.34l-.04.04a2 2 0 0 1-2.83-2.83l.04-.04A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.56-1.04H3a2 2 0 0 1 0-4h.06A1.7 1.7 0 0 0 4.6 8.92a1.7 1.7 0 0 0-.34-1.87l-.04-.04a2 2 0 0 1 2.83-2.83l.04.04a1.7 1.7 0 0 0 1.87.34A1.7 1.7 0 0 0 10 3.06V3a2 2 0 0 1 4 0v.06a1.7 1.7 0 0 0 1.04 1.56 1.7 1.7 0 0 0 1.87-.34l.04-.04a2 2 0 0 1 2.83 2.83l-.04.04a1.7 1.7 0 0 0-.34 1.87 1.7 1.7 0 0 0 1.56 1.04H21a2 2 0 0 1 0 4h-.06A1.7 1.7 0 0 0 19.4 15Z',
+ ],
+ SlidersHorizontal: [
+ 'M4 7h4M12 7h8',
+ 'M4 17h10M18 17h2',
+ 'M8 10a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z',
+ 'M16 20a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z',
+ ],
+ User: [
+ 'M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z',
+ 'M4 21a8 8 0 0 1 16 0',
+ ],
+ Users: [
+ 'M9 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z',
+ 'M2 21a7 7 0 0 1 14 0',
+ 'M17 11a3 3 0 1 0 0-6',
+ 'M17 15a5.5 5.5 0 0 1 5 6',
+ ],
+};
+
+@Component({
+ selector: 'app-icon',
+ standalone: false,
+ template: `
+
+ `,
+})
+export class AppIconComponent {
+ @Input() name = 'FileText';
+ @Input() size = 20;
+ @Input() strokeWidth = 2;
+
+ get paths(): string[] {
+ return ICON_PATHS[this.name] || ICON_PATHS['FileText'];
+ }
+}
@NgModule({
- imports: [],
- exports: [],
+ declarations: [AppIconComponent],
+ imports: [CommonModule],
+ exports: [AppIconComponent],
})
export class IconModule {}