diff --git a/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.html b/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.html index 4626c566..e5e29321 100644 --- a/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.html +++ b/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.html @@ -1,4 +1,7 @@ - +
diff --git a/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.scss b/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.scss index 8b9ebee6..d7a4130d 100644 --- a/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.scss +++ b/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.scss @@ -113,17 +113,53 @@ // Prompt styling ::ng-deep [prompt] { - font-size: 13px; - color: #7f11e0; - font-weight: 500; + font-size: 14px; + color: #d32f2f; + font-weight: 600; display: flex; align-items: center; - gap: 4px; - animation: pulse 2s infinite; + gap: 6px; + animation: urgent-pulse 1.5s infinite; + text-shadow: 0 0 8px rgba(211, 47, 47, 0.3); + letter-spacing: 0.5px; - &:before { - content: '👆'; - font-size: 16px; + span { + position: relative; + + &::after { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, + transparent, + #d32f2f, + transparent + ); + animation: underline-slide 2s infinite; + } + } +} + +@keyframes urgent-pulse { + 0%, 100% { + opacity: 0.9; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.02); + } +} + +@keyframes underline-slide { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); } } @@ -139,6 +175,97 @@ } } +// Zero value styling +.dashboard-card.zero-value { + background: linear-gradient(135deg, #fff5f5 0%, #ffe0e0 100%); + border: 2px solid #ff6b6b; + position: relative; + overflow: hidden; + box-shadow: 0 0 20px rgba(255, 107, 107, 0.2); + animation: glow-pulse 2.5s infinite; + + &::before { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + background: linear-gradient(90deg, + transparent, + rgba(255, 107, 107, 0.3), + transparent + ); + animation: shimmer 3s infinite; + } + + .card-value { + color: #d32f2f !important; + font-weight: 700 !important; + text-shadow: 0 0 3px rgba(211, 47, 47, 0.2); + } + + .icon-container { + animation: attention-pulse 2s infinite; + box-shadow: 0 0 15px rgba(255, 107, 107, 0.3); + } + + &:hover { + transform: translateY(-2px) scale(1.02); + box-shadow: 0 4px 30px rgba(255, 107, 107, 0.3); + } +} + +@keyframes glow-pulse { + 0%, 100% { + box-shadow: 0 0 20px rgba(255, 107, 107, 0.2); + } + 50% { + box-shadow: 0 0 30px rgba(255, 107, 107, 0.4); + } +} + +// Shake animation for zero value cards +.dashboard-card.shake-animation { + animation: subtle-shake 4s infinite; +} + +@keyframes subtle-shake { + 0%, 90%, 100% { + transform: translateX(0); + } + 92% { + transform: translateX(-2px) rotate(-0.5deg); + } + 94% { + transform: translateX(2px) rotate(0.5deg); + } + 96% { + transform: translateX(-1px) rotate(-0.3deg); + } + 98% { + transform: translateX(1px) rotate(0.3deg); + } +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(200%); + } +} + +@keyframes attention-pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } +} + // Dark theme :host-context(.dark-theme) { .dashboard-card { @@ -165,9 +292,35 @@ color: #bbb; } } + + &.zero-value { + background: linear-gradient(135deg, #4a1a1a 0%, #3d1515 100%); + border: 2px solid #ff6b6b; + + &::before { + background: linear-gradient(90deg, + transparent, + rgba(255, 107, 107, 0.2), + transparent + ); + } + + .card-value { + color: #ff8a80 !important; + } + } } ::ng-deep [prompt] { - color: #bb86fc; + color: #ff8a80; + text-shadow: 0 0 10px rgba(255, 138, 128, 0.5); + + span::after { + background: linear-gradient(90deg, + transparent, + #ff8a80, + transparent + ); + } } } diff --git a/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.ts b/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.ts index 1070453d..79b4d9d3 100644 --- a/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.ts +++ b/src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.ts @@ -21,6 +21,7 @@ export class DfDashboardCardComponent { @Input() trendClass?: string; @Input() footerText?: string; @Input() showPrompt?: boolean = false; + @Input() isZero?: boolean = false; @Input() color: 'primary' | 'accent' | 'success' | 'info' | 'warn' = 'primary'; } diff --git a/src/app/adf-home/df-dashboard/df-dashboard.component.html b/src/app/adf-home/df-dashboard/df-dashboard.component.html index adef0bcf..2ada076a 100644 --- a/src/app/adf-home/df-dashboard/df-dashboard.component.html +++ b/src/app/adf-home/df-dashboard/df-dashboard.component.html @@ -21,6 +21,7 @@

{{ 'home.dashboard.title' | transloco }}

[value]="stats.services.total" [subtitle]="'home.dashboard.services.total' | transloco" [showPrompt]="stats.services.total === 0" + [isZero]="stats.services.total === 0" color="primary">
{{ 'home.dashboard.services.createPrompt' | transloco }} @@ -33,7 +34,12 @@

{{ 'home.dashboard.title' | transloco }}

[title]="'home.dashboard.apiKeys.title' | transloco" [value]="stats.apiKeys.total" [subtitle]="'home.dashboard.apiKeys.total' | transloco" + [showPrompt]="stats.apiKeys.total === 0" + [isZero]="stats.apiKeys.total === 0" color="success"> +
+ {{ 'home.dashboard.apiKeys.createPrompt' | transloco }} +
@@ -42,7 +48,12 @@

{{ 'home.dashboard.title' | transloco }}

[title]="'home.dashboard.roles.title' | transloco" [value]="stats.roles.total" [subtitle]="'home.dashboard.roles.total' | transloco" + [showPrompt]="stats.roles.total === 0" + [isZero]="stats.roles.total === 0" color="info"> +
+ {{ 'home.dashboard.roles.createPrompt' | transloco }} +
diff --git a/src/app/adf-services/df-manage-services/df-manage-services-table.component.ts b/src/app/adf-services/df-manage-services/df-manage-services-table.component.ts index 7d5e9d16..6a68642a 100644 --- a/src/app/adf-services/df-manage-services/df-manage-services-table.component.ts +++ b/src/app/adf-services/df-manage-services/df-manage-services-table.component.ts @@ -27,7 +27,10 @@ import { catchError, throwError } from 'rxjs'; standalone: true, imports: DfManageTableModules, }) -export class DfManageServicesTableComponent extends DfManageTableComponent implements OnInit { +export class DfManageServicesTableComponent + extends DfManageTableComponent + implements OnInit +{ serviceTypes: Array = []; system = false; constructor( @@ -41,15 +44,18 @@ export class DfManageServicesTableComponent extends DfManageTableComponent { + this._activatedRoute.data.subscribe(routeData => { const { data } = routeData; - this.system = routeData['system'] || this._activatedRoute.snapshot.parent?.data?.['system'] || false; + this.system = + routeData['system'] || + this._activatedRoute.snapshot.parent?.data?.['system'] || + false; this.serviceTypes = data?.serviceTypes; this.allowCreate = !this.system; if (this.system) { diff --git a/src/app/adf-services/df-service-details/df-service-details.component.ts b/src/app/adf-services/df-service-details/df-service-details.component.ts index 9851a755..375d3604 100644 --- a/src/app/adf-services/df-service-details/df-service-details.component.ts +++ b/src/app/adf-services/df-service-details/df-service-details.component.ts @@ -384,9 +384,7 @@ export class DfServiceDetailsComponent implements OnInit { if (serviceType === 'local_email') { return false; } - return ( - serviceType && this.configSchema?.length === 0 - ); + return serviceType && this.configSchema?.length === 0; } get scriptMode() { diff --git a/src/app/adf-services/resolvers/services.resolver.ts b/src/app/adf-services/resolvers/services.resolver.ts index 613bc7a9..b94a5b29 100644 --- a/src/app/adf-services/resolvers/services.resolver.ts +++ b/src/app/adf-services/resolvers/services.resolver.ts @@ -21,8 +21,10 @@ export const servicesResolver = const serviceTypeService = inject(SERVICE_TYPE_SERVICE_TOKEN); const servicesService = inject(SERVICES_SERVICE_TOKEN); - const system: boolean = route.data['system'] || route.parent?.data?.['system'] || false; - const groups: Array = route.data['groups'] || route.parent?.data?.['groups']; + const system: boolean = + route.data['system'] || route.parent?.data?.['system'] || false; + const groups: Array = + route.data['groups'] || route.parent?.data?.['groups']; if (groups) { const filteredGroups = groups.map(grp => @@ -44,7 +46,9 @@ export const servicesResolver = limit, sort: 'name', filter: `${ - system ? '(created_by_id is null) and (name != "api_docs") and ' : '' + system + ? '(created_by_id is null) and (name != "api_docs") and ' + : '' }(type in ("${serviceTypes.map(src => src.name).join('","')}"))${ filter ? ` and ${filter}` : '' }`, diff --git a/src/assets/i18n/home/en.json b/src/assets/i18n/home/en.json index f79163f1..a525c476 100644 --- a/src/assets/i18n/home/en.json +++ b/src/assets/i18n/home/en.json @@ -99,7 +99,7 @@ "services": { "title": "API Services", "total": "Your Services", - "createPrompt": "Get started! Create your first API using the cards above" + "createPrompt": "🚀 Get started! Create your first API service above!" }, "admins": { "title": "Admins", @@ -107,11 +107,13 @@ }, "apiKeys": { "title": "API Keys", - "total": "Your API Keys" + "total": "Your API Keys", + "createPrompt": "🔑 Secure your APIs! Generate an API key now!" }, "roles": { "title": "Roles", - "total": "Your Roles" + "total": "Your Roles", + "createPrompt": "🔒 Control access! Set up your first role!" } } } \ No newline at end of file