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" > {{ item.label }} 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 {}