Skip to content

Commit 28859a8

Browse files
authored
Merge pull request #440 from dreamfactorysoftware/claude-stats
Made Quick Stats card pop with alerts if nothing is configured
2 parents 94bdb9d + 6c8fb90 commit 28859a8

8 files changed

Lines changed: 202 additions & 24 deletions

File tree

src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
<mat-card class="dashboard-card" [class]="'card-' + color">
1+
<mat-card class="dashboard-card"
2+
[class]="'card-' + color"
3+
[class.zero-value]="isZero"
4+
[class.shake-animation]="isZero">
25
<mat-card-content>
36
<div class="card-header">
47
<div class="icon-container" [class]="'icon-' + color">

src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.scss

Lines changed: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,53 @@
113113

114114
// Prompt styling
115115
::ng-deep [prompt] {
116-
font-size: 13px;
117-
color: #7f11e0;
118-
font-weight: 500;
116+
font-size: 14px;
117+
color: #d32f2f;
118+
font-weight: 600;
119119
display: flex;
120120
align-items: center;
121-
gap: 4px;
122-
animation: pulse 2s infinite;
121+
gap: 6px;
122+
animation: urgent-pulse 1.5s infinite;
123+
text-shadow: 0 0 8px rgba(211, 47, 47, 0.3);
124+
letter-spacing: 0.5px;
123125

124-
&:before {
125-
content: '👆';
126-
font-size: 16px;
126+
span {
127+
position: relative;
128+
129+
&::after {
130+
content: '';
131+
position: absolute;
132+
bottom: -2px;
133+
left: 0;
134+
right: 0;
135+
height: 2px;
136+
background: linear-gradient(90deg,
137+
transparent,
138+
#d32f2f,
139+
transparent
140+
);
141+
animation: underline-slide 2s infinite;
142+
}
143+
}
144+
}
145+
146+
@keyframes urgent-pulse {
147+
0%, 100% {
148+
opacity: 0.9;
149+
transform: scale(1);
150+
}
151+
50% {
152+
opacity: 1;
153+
transform: scale(1.02);
154+
}
155+
}
156+
157+
@keyframes underline-slide {
158+
0% {
159+
transform: translateX(-100%);
160+
}
161+
100% {
162+
transform: translateX(100%);
127163
}
128164
}
129165

@@ -139,6 +175,97 @@
139175
}
140176
}
141177

178+
// Zero value styling
179+
.dashboard-card.zero-value {
180+
background: linear-gradient(135deg, #fff5f5 0%, #ffe0e0 100%);
181+
border: 2px solid #ff6b6b;
182+
position: relative;
183+
overflow: hidden;
184+
box-shadow: 0 0 20px rgba(255, 107, 107, 0.2);
185+
animation: glow-pulse 2.5s infinite;
186+
187+
&::before {
188+
content: '';
189+
position: absolute;
190+
top: -2px;
191+
left: -2px;
192+
right: -2px;
193+
bottom: -2px;
194+
background: linear-gradient(90deg,
195+
transparent,
196+
rgba(255, 107, 107, 0.3),
197+
transparent
198+
);
199+
animation: shimmer 3s infinite;
200+
}
201+
202+
.card-value {
203+
color: #d32f2f !important;
204+
font-weight: 700 !important;
205+
text-shadow: 0 0 3px rgba(211, 47, 47, 0.2);
206+
}
207+
208+
.icon-container {
209+
animation: attention-pulse 2s infinite;
210+
box-shadow: 0 0 15px rgba(255, 107, 107, 0.3);
211+
}
212+
213+
&:hover {
214+
transform: translateY(-2px) scale(1.02);
215+
box-shadow: 0 4px 30px rgba(255, 107, 107, 0.3);
216+
}
217+
}
218+
219+
@keyframes glow-pulse {
220+
0%, 100% {
221+
box-shadow: 0 0 20px rgba(255, 107, 107, 0.2);
222+
}
223+
50% {
224+
box-shadow: 0 0 30px rgba(255, 107, 107, 0.4);
225+
}
226+
}
227+
228+
// Shake animation for zero value cards
229+
.dashboard-card.shake-animation {
230+
animation: subtle-shake 4s infinite;
231+
}
232+
233+
@keyframes subtle-shake {
234+
0%, 90%, 100% {
235+
transform: translateX(0);
236+
}
237+
92% {
238+
transform: translateX(-2px) rotate(-0.5deg);
239+
}
240+
94% {
241+
transform: translateX(2px) rotate(0.5deg);
242+
}
243+
96% {
244+
transform: translateX(-1px) rotate(-0.3deg);
245+
}
246+
98% {
247+
transform: translateX(1px) rotate(0.3deg);
248+
}
249+
}
250+
251+
@keyframes shimmer {
252+
0% {
253+
transform: translateX(-100%);
254+
}
255+
100% {
256+
transform: translateX(200%);
257+
}
258+
}
259+
260+
@keyframes attention-pulse {
261+
0%, 100% {
262+
transform: scale(1);
263+
}
264+
50% {
265+
transform: scale(1.1);
266+
}
267+
}
268+
142269
// Dark theme
143270
:host-context(.dark-theme) {
144271
.dashboard-card {
@@ -165,9 +292,35 @@
165292
color: #bbb;
166293
}
167294
}
295+
296+
&.zero-value {
297+
background: linear-gradient(135deg, #4a1a1a 0%, #3d1515 100%);
298+
border: 2px solid #ff6b6b;
299+
300+
&::before {
301+
background: linear-gradient(90deg,
302+
transparent,
303+
rgba(255, 107, 107, 0.2),
304+
transparent
305+
);
306+
}
307+
308+
.card-value {
309+
color: #ff8a80 !important;
310+
}
311+
}
168312
}
169313

170314
::ng-deep [prompt] {
171-
color: #bb86fc;
315+
color: #ff8a80;
316+
text-shadow: 0 0 10px rgba(255, 138, 128, 0.5);
317+
318+
span::after {
319+
background: linear-gradient(90deg,
320+
transparent,
321+
#ff8a80,
322+
transparent
323+
);
324+
}
172325
}
173326
}

src/app/adf-home/df-dashboard/df-dashboard-card/df-dashboard-card.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class DfDashboardCardComponent {
2121
@Input() trendClass?: string;
2222
@Input() footerText?: string;
2323
@Input() showPrompt?: boolean = false;
24+
@Input() isZero?: boolean = false;
2425
@Input() color: 'primary' | 'accent' | 'success' | 'info' | 'warn' =
2526
'primary';
2627
}

src/app/adf-home/df-dashboard/df-dashboard.component.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ <h2 class="dashboard-title">{{ 'home.dashboard.title' | transloco }}</h2>
2121
[value]="stats.services.total"
2222
[subtitle]="'home.dashboard.services.total' | transloco"
2323
[showPrompt]="stats.services.total === 0"
24+
[isZero]="stats.services.total === 0"
2425
color="primary">
2526
<div prompt class="service-prompt" *ngIf="stats.services.total === 0">
2627
<span>{{ 'home.dashboard.services.createPrompt' | transloco }}</span>
@@ -33,7 +34,12 @@ <h2 class="dashboard-title">{{ 'home.dashboard.title' | transloco }}</h2>
3334
[title]="'home.dashboard.apiKeys.title' | transloco"
3435
[value]="stats.apiKeys.total"
3536
[subtitle]="'home.dashboard.apiKeys.total' | transloco"
37+
[showPrompt]="stats.apiKeys.total === 0"
38+
[isZero]="stats.apiKeys.total === 0"
3639
color="success">
40+
<div prompt class="key-prompt" *ngIf="stats.apiKeys.total === 0">
41+
<span>{{ 'home.dashboard.apiKeys.createPrompt' | transloco }}</span>
42+
</div>
3743
</df-dashboard-card>
3844

3945
<!-- Roles Card -->
@@ -42,7 +48,12 @@ <h2 class="dashboard-title">{{ 'home.dashboard.title' | transloco }}</h2>
4248
[title]="'home.dashboard.roles.title' | transloco"
4349
[value]="stats.roles.total"
4450
[subtitle]="'home.dashboard.roles.total' | transloco"
51+
[showPrompt]="stats.roles.total === 0"
52+
[isZero]="stats.roles.total === 0"
4553
color="info">
54+
<div prompt class="role-prompt" *ngIf="stats.roles.total === 0">
55+
<span>{{ 'home.dashboard.roles.createPrompt' | transloco }}</span>
56+
</div>
4657
</df-dashboard-card>
4758
</div>
4859
</div>

src/app/adf-services/df-manage-services/df-manage-services-table.component.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import { catchError, throwError } from 'rxjs';
2727
standalone: true,
2828
imports: DfManageTableModules,
2929
})
30-
export class DfManageServicesTableComponent extends DfManageTableComponent<ServiceRow> implements OnInit {
30+
export class DfManageServicesTableComponent
31+
extends DfManageTableComponent<ServiceRow>
32+
implements OnInit
33+
{
3134
serviceTypes: Array<ServiceType> = [];
3235
system = false;
3336
constructor(
@@ -41,15 +44,18 @@ export class DfManageServicesTableComponent extends DfManageTableComponent<Servi
4144
) {
4245
super(router, activatedRoute, liveAnnouncer, translateService, dialog);
4346
}
44-
47+
4548
override ngOnInit(): void {
4649
// Call parent's ngOnInit first to set up the data source
4750
super.ngOnInit();
48-
51+
4952
// Then subscribe to route data for additional setup
50-
this._activatedRoute.data.subscribe((routeData) => {
53+
this._activatedRoute.data.subscribe(routeData => {
5154
const { data } = routeData;
52-
this.system = routeData['system'] || this._activatedRoute.snapshot.parent?.data?.['system'] || false;
55+
this.system =
56+
routeData['system'] ||
57+
this._activatedRoute.snapshot.parent?.data?.['system'] ||
58+
false;
5359
this.serviceTypes = data?.serviceTypes;
5460
this.allowCreate = !this.system;
5561
if (this.system) {

src/app/adf-services/df-service-details/df-service-details.component.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,7 @@ export class DfServiceDetailsComponent implements OnInit {
384384
if (serviceType === 'local_email') {
385385
return false;
386386
}
387-
return (
388-
serviceType && this.configSchema?.length === 0
389-
);
387+
return serviceType && this.configSchema?.length === 0;
390388
}
391389

392390
get scriptMode() {

src/app/adf-services/resolvers/services.resolver.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ export const servicesResolver =
2121
const serviceTypeService = inject(SERVICE_TYPE_SERVICE_TOKEN);
2222
const servicesService = inject(SERVICES_SERVICE_TOKEN);
2323

24-
const system: boolean = route.data['system'] || route.parent?.data?.['system'] || false;
25-
const groups: Array<string> = route.data['groups'] || route.parent?.data?.['groups'];
24+
const system: boolean =
25+
route.data['system'] || route.parent?.data?.['system'] || false;
26+
const groups: Array<string> =
27+
route.data['groups'] || route.parent?.data?.['groups'];
2628

2729
if (groups) {
2830
const filteredGroups = groups.map(grp =>
@@ -44,7 +46,9 @@ export const servicesResolver =
4446
limit,
4547
sort: 'name',
4648
filter: `${
47-
system ? '(created_by_id is null) and (name != "api_docs") and ' : ''
49+
system
50+
? '(created_by_id is null) and (name != "api_docs") and '
51+
: ''
4852
}(type in ("${serviceTypes.map(src => src.name).join('","')}"))${
4953
filter ? ` and ${filter}` : ''
5054
}`,

src/assets/i18n/home/en.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,21 @@
9999
"services": {
100100
"title": "API Services",
101101
"total": "Your Services",
102-
"createPrompt": "Get started! Create your first API using the cards above"
102+
"createPrompt": "🚀 Get started! Create your first API service above!"
103103
},
104104
"admins": {
105105
"title": "Admins",
106106
"total": "Total Admins"
107107
},
108108
"apiKeys": {
109109
"title": "API Keys",
110-
"total": "Your API Keys"
110+
"total": "Your API Keys",
111+
"createPrompt": "🔑 Secure your APIs! Generate an API key now!"
111112
},
112113
"roles": {
113114
"title": "Roles",
114-
"total": "Your Roles"
115+
"total": "Your Roles",
116+
"createPrompt": "🔒 Control access! Set up your first role!"
115117
}
116118
}
117119
}

0 commit comments

Comments
 (0)