Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 192 additions & 21 deletions frontend/src/app/components/auto-configure/auto-configure.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
display: flex;
flex-direction: column;
align-items: center;
padding: 48px 32px 36px;
gap: 16px;
padding: 40px 32px 24px;
gap: 10px;
}

.card__title {
Expand All @@ -64,6 +64,16 @@
}
}

.card__head-detail {
width: 100%;
min-height: 64px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
}

.card__subtitle {
margin: 0;
font-size: 14px;
Expand Down Expand Up @@ -160,7 +170,6 @@
position: relative;
width: 72px;
height: 72px;
margin-bottom: 8px;
}

.spinner__ring {
Expand Down Expand Up @@ -191,33 +200,195 @@
to { transform: rotate(-360deg); }
}

/* ── Indeterminate progress bar ── */
/* ── Success checkmark (replaces spinner on complete) ── */

.success-mark {
width: 72px;
height: 72px;
}

.progress-bar {
.success-mark svg {
width: 100%;
max-width: 320px;
height: 4px;
border-radius: 2px;
background: var(--color-primaryPalette-200);
overflow: hidden;
height: 100%;
overflow: visible;
}

.success-mark__circle {
fill: none;
stroke: var(--color-successPalette-500);
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 158;
stroke-dashoffset: 158;
animation: success-circle 500ms ease-out forwards;
transform-origin: 50% 50%;
}

.success-mark__check {
fill: none;
stroke: var(--color-successPalette-500);
stroke-width: 3.5;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 36;
stroke-dashoffset: 36;
animation: success-check 350ms 400ms ease-out forwards;
}

@keyframes success-circle {
to { stroke-dashoffset: 0; }
}

@keyframes success-check {
to { stroke-dashoffset: 0; }
}

/* ── Summary chips (shown on complete) ── */

.summary-chips {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
margin-top: 4px;
}

.summary-chip {
display: inline-flex;
align-items: baseline;
gap: 4px;
padding: 4px 10px;
border-radius: 999px;
background: var(--color-successPalette-50);
color: var(--color-successPalette-700);
font-size: 12px;
font-weight: 500;
line-height: 1.4;
white-space: nowrap;
animation: chip-enter 280ms ease-out;
}

.summary-chip strong {
font-size: 13px;
font-weight: 700;
}

@media (prefers-color-scheme: dark) {
.progress-bar {
background: var(--color-primaryPalette-800);
.summary-chip {
background: var(--color-successPalette-900);
color: var(--color-successPalette-100);
}
}

.progress-bar__fill {
width: 40%;
height: 100%;
border-radius: 2px;
background: var(--color-accentedPalette-500);
animation: indeterminate 1.5s ease-in-out infinite;
@keyframes chip-enter {
from { transform: scale(0.85); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}

@keyframes indeterminate {
0% { transform: translateX(-100%); }
100% { transform: translateX(350%); }
/* ── Live status log ── */

.status-log {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
max-width: 360px;
height: 78px;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 8px;
overflow: hidden;
mask-image: linear-gradient(to bottom, transparent 0, black 28px);
-webkit-mask-image: linear-gradient(to bottom, transparent 0, black 28px);
}

.status-log__item {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
color: var(--color-primaryPalette-400);
text-align: left;
line-height: 1.35;
animation: status-log-enter 240ms ease-out;
}

.status-log__item_done {
color: var(--color-primaryPalette-200);
font-size: 12px;
}

.status-log__item_current {
color: var(--color-primaryPalette-800);
font-weight: 500;
}

@media (prefers-color-scheme: dark) {
.status-log__item_done {
color: var(--color-primaryPalette-500);
}
.status-log__item_current {
color: var(--color-primaryPalette-50);
}
}

.status-log__icon {
flex-shrink: 0;
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
}

.status-log__check {
color: var(--color-primaryPalette-200);
font-size: 13px;
font-weight: 700;
line-height: 1;
}

@media (prefers-color-scheme: dark) {
.status-log__check {
color: var(--color-primaryPalette-500);
}
}

.status-log__spinner {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid var(--color-accentedPalette-200);
border-top-color: var(--color-accentedPalette-500);
animation: tiny-spin 0.8s linear infinite;
}

@keyframes tiny-spin {
to { transform: rotate(360deg); }
}

@keyframes status-log-enter {
from { transform: translateY(-6px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}

.card__status {
margin: 0;
min-height: 20px;
font-size: 13px;
color: var(--color-primaryPalette-500);
text-align: center;
line-height: 1.4;
max-width: 360px;
}

.card__status_error {
color: var(--color-warnPalette-500);
}

@media (prefers-color-scheme: dark) {
.card__status {
color: var(--color-primaryPalette-200);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
<div class="page">
<div class="card">
<div class="card__body">
<div class="spinner">
<div *ngIf="!done()" class="spinner">
<div class="spinner__ring spinner__ring--outer"></div>
<div class="spinner__ring spinner__ring--inner"></div>
</div>
<h2 class="card__title">Configuring your database</h2>
<p class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p>
<div class="progress-bar">
<div class="progress-bar__fill"></div>
<div *ngIf="done()" class="success-mark" aria-label="Configuration complete">
<svg viewBox="0 0 52 52" xmlns="http://www.w3.org/2000/svg">
<circle class="success-mark__circle" cx="26" cy="26" r="25"></circle>
<path class="success-mark__check" d="M14 27 l8 8 l16 -18"></path>
</svg>
</div>
<h2 class="card__title">{{ done() ? 'Your database is ready' : 'Configuring your database' }}</h2>
<div class="card__head-detail">
<p *ngIf="!done()" class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p>
<div *ngIf="hasSummary()" class="summary-chips">
<span class="summary-chip"><strong>{{ tablesConfigured().size }}</strong> {{ tablesConfigured().size === 1 ? 'table' : 'tables' }}</span>
<span *ngIf="widgetsCount() > 0" class="summary-chip"><strong>{{ widgetsCount() }}</strong> {{ widgetsCount() === 1 ? 'widget' : 'widgets' }}</span>
<span *ngIf="settingsCount() > 0" class="summary-chip"><strong>{{ settingsCount() }}</strong> {{ settingsCount() === 1 ? 'setting' : 'settings' }}</span>
</div>
<p *ngIf="done() && !hasSummary()" class="card__subtitle">All set — opening your dashboard…</p>
</div>

<ul class="status-log" *ngIf="!errorText(); else errorBlock">
<li *ngFor="let m of messages(); let last = last; trackBy: trackByIndex"
class="status-log__item"
[class.status-log__item_current]="last"
[class.status-log__item_done]="!last">
<span class="status-log__icon" aria-hidden="true">
<span *ngIf="!last" class="status-log__check">&#10003;</span>
<span *ngIf="last" class="status-log__spinner"></span>
</span>
<span class="status-log__text">{{ m }}</span>
</li>
</ul>
<ng-template #errorBlock>
<p class="card__status card__status_error">{{ errorText() }}</p>
</ng-template>
Comment on lines +4 to +39

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=ts -C2 '\btrackByIndex\s*[=(:]' frontend/src/app/components/auto-configure/

Repository: rocket-admin/rocketadmin

Length of output: 500


🏁 Script executed:

#!/bin/bash
set -euo pipefail
nl -ba frontend/src/app/components/auto-configure/auto-configure.component.html | sed -n '1,140p'

Repository: rocket-admin/rocketadmin

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n frontend/src/app/components/auto-configure/auto-configure.component.html | sed -n '1,120p'

Repository: rocket-admin/rocketadmin

Length of output: 2840


Replace structural directives with Angular built-in control flow in auto-configure template

frontend/src/app/components/auto-configure/auto-configure.component.html still uses *ngIf/*ngFor (including else errorBlock) and trackBy: trackByIndex in the template region; update to @if/@for to match the project guideline.

♻️ Proposed conversion to built-in control flow
-			<div *ngIf="!done()" class="spinner">
-				<div class="spinner__ring spinner__ring--outer"></div>
-				<div class="spinner__ring spinner__ring--inner"></div>
-			</div>
-			<div *ngIf="done()" class="success-mark" aria-label="Configuration complete">
-				<svg viewBox="0 0 52 52" xmlns="http://www.w3.org/2000/svg">
-					<circle class="success-mark__circle" cx="26" cy="26" r="25"></circle>
-					<path class="success-mark__check" d="M14 27 l8 8 l16 -18"></path>
-				</svg>
-			</div>
+			`@if` (!done()) {
+				<div class="spinner">
+					<div class="spinner__ring spinner__ring--outer"></div>
+					<div class="spinner__ring spinner__ring--inner"></div>
+				</div>
+			} `@else` {
+				<div class="success-mark" aria-label="Configuration complete">
+					<svg viewBox="0 0 52 52" xmlns="http://www.w3.org/2000/svg">
+						<circle class="success-mark__circle" cx="26" cy="26" r="25"></circle>
+						<path class="success-mark__check" d="M14 27 l8 8 l16 -18"></path>
+					</svg>
+				</div>
+			}
 			<h2 class="card__title">{{ done() ? 'Your database is ready' : 'Configuring your database' }}</h2>
 			<div class="card__head-detail">
-				<p *ngIf="!done()" class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p>
-				<div *ngIf="hasSummary()" class="summary-chips">
-					<span class="summary-chip"><strong>{{ tablesConfigured().size }}</strong> {{ tablesConfigured().size === 1 ? 'table' : 'tables' }}</span>
-					<span *ngIf="widgetsCount() > 0" class="summary-chip"><strong>{{ widgetsCount() }}</strong> {{ widgetsCount() === 1 ? 'widget' : 'widgets' }}</span>
-					<span *ngIf="settingsCount() > 0" class="summary-chip"><strong>{{ settingsCount() }}</strong> {{ settingsCount() === 1 ? 'setting' : 'settings' }}</span>
-				</div>
-				<p *ngIf="done() && !hasSummary()" class="card__subtitle">All set — opening your dashboard…</p>
+				`@if` (!done()) {
+					<p class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p>
+				}
+				`@if` (hasSummary()) {
+					<div class="summary-chips">
+						<span class="summary-chip"><strong>{{ tablesConfigured().size }}</strong> {{ tablesConfigured().size === 1 ? 'table' : 'tables' }}</span>
+						`@if` (widgetsCount() > 0) {
+							<span class="summary-chip"><strong>{{ widgetsCount() }}</strong> {{ widgetsCount() === 1 ? 'widget' : 'widgets' }}</span>
+						}
+						`@if` (settingsCount() > 0) {
+							<span class="summary-chip"><strong>{{ settingsCount() }}</strong> {{ settingsCount() === 1 ? 'setting' : 'settings' }}</span>
+						}
+					</div>
+				}
+				`@if` (done() && !hasSummary()) {
+					<p class="card__subtitle">All set — opening your dashboard…</p>
+				}
 			</div>
 
-			<ul class="status-log" *ngIf="!errorText(); else errorBlock">
-				<li *ngFor="let m of messages(); let last = last; trackBy: trackByIndex"
-					class="status-log__item"
-					[class.status-log__item_current]="last"
-					[class.status-log__item_done]="!last">
-					<span class="status-log__icon" aria-hidden="true">
-						<span *ngIf="!last" class="status-log__check">&`#10003`;</span>
-						<span *ngIf="last" class="status-log__spinner"></span>
-					</span>
-					<span class="status-log__text">{{ m }}</span>
-				</li>
-			</ul>
-			<ng-template `#errorBlock`>
-				<p class="card__status card__status_error">{{ errorText() }}</p>
-			</ng-template>
+			`@if` (!errorText()) {
+				<ul class="status-log">
+					`@for` (m of messages(); track $index; let last = $last) {
+						<li class="status-log__item"
+							[class.status-log__item_current]="last"
+							[class.status-log__item_done]="!last">
+							<span class="status-log__icon" aria-hidden="true">
+								`@if` (!last) {
+									<span class="status-log__check">&`#10003`;</span>
+								} `@else` {
+									<span class="status-log__spinner"></span>
+								}
+							</span>
+							<span class="status-log__text">{{ m }}</span>
+						</li>
+					}
+				</ul>
+			} `@else` {
+				<p class="card__status card__status_error">{{ errorText() }}</p>
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div *ngIf="!done()" class="spinner">
<div class="spinner__ring spinner__ring--outer"></div>
<div class="spinner__ring spinner__ring--inner"></div>
</div>
<h2 class="card__title">Configuring your database</h2>
<p class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p>
<div class="progress-bar">
<div class="progress-bar__fill"></div>
<div *ngIf="done()" class="success-mark" aria-label="Configuration complete">
<svg viewBox="0 0 52 52" xmlns="http://www.w3.org/2000/svg">
<circle class="success-mark__circle" cx="26" cy="26" r="25"></circle>
<path class="success-mark__check" d="M14 27 l8 8 l16 -18"></path>
</svg>
</div>
<h2 class="card__title">{{ done() ? 'Your database is ready' : 'Configuring your database' }}</h2>
<div class="card__head-detail">
<p *ngIf="!done()" class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p>
<div *ngIf="hasSummary()" class="summary-chips">
<span class="summary-chip"><strong>{{ tablesConfigured().size }}</strong> {{ tablesConfigured().size === 1 ? 'table' : 'tables' }}</span>
<span *ngIf="widgetsCount() > 0" class="summary-chip"><strong>{{ widgetsCount() }}</strong> {{ widgetsCount() === 1 ? 'widget' : 'widgets' }}</span>
<span *ngIf="settingsCount() > 0" class="summary-chip"><strong>{{ settingsCount() }}</strong> {{ settingsCount() === 1 ? 'setting' : 'settings' }}</span>
</div>
<p *ngIf="done() && !hasSummary()" class="card__subtitle">All set — opening your dashboard…</p>
</div>
<ul class="status-log" *ngIf="!errorText(); else errorBlock">
<li *ngFor="let m of messages(); let last = last; trackBy: trackByIndex"
class="status-log__item"
[class.status-log__item_current]="last"
[class.status-log__item_done]="!last">
<span class="status-log__icon" aria-hidden="true">
<span *ngIf="!last" class="status-log__check">&#10003;</span>
<span *ngIf="last" class="status-log__spinner"></span>
</span>
<span class="status-log__text">{{ m }}</span>
</li>
</ul>
<ng-template #errorBlock>
<p class="card__status card__status_error">{{ errorText() }}</p>
</ng-template>
`@if` (!done()) {
<div class="spinner">
<div class="spinner__ring spinner__ring--outer"></div>
<div class="spinner__ring spinner__ring--inner"></div>
</div>
} `@else` {
<div class="success-mark" aria-label="Configuration complete">
<svg viewBox="0 0 52 52" xmlns="http://www.w3.org/2000/svg">
<circle class="success-mark__circle" cx="26" cy="26" r="25"></circle>
<path class="success-mark__check" d="M14 27 l8 8 l16 -18"></path>
</svg>
</div>
}
<h2 class="card__title">{{ done() ? 'Your database is ready' : 'Configuring your database' }}</h2>
<div class="card__head-detail">
`@if` (!done()) {
<p class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p>
}
`@if` (hasSummary()) {
<div class="summary-chips">
<span class="summary-chip"><strong>{{ tablesConfigured().size }}</strong> {{ tablesConfigured().size === 1 ? 'table' : 'tables' }}</span>
`@if` (widgetsCount() > 0) {
<span class="summary-chip"><strong>{{ widgetsCount() }}</strong> {{ widgetsCount() === 1 ? 'widget' : 'widgets' }}</span>
}
`@if` (settingsCount() > 0) {
<span class="summary-chip"><strong>{{ settingsCount() }}</strong> {{ settingsCount() === 1 ? 'setting' : 'settings' }}</span>
}
</div>
}
`@if` (done() && !hasSummary()) {
<p class="card__subtitle">All set — opening your dashboard…</p>
}
</div>
`@if` (!errorText()) {
<ul class="status-log">
`@for` (m of messages(); track $index; let last = $last) {
<li class="status-log__item"
[class.status-log__item_current]="last"
[class.status-log__item_done]="!last">
<span class="status-log__icon" aria-hidden="true">
`@if` (!last) {
<span class="status-log__check">&`#10003`;</span>
} `@else` {
<span class="status-log__spinner"></span>
}
</span>
<span class="status-log__text">{{ m }}</span>
</li>
}
</ul>
} `@else` {
<p class="card__status card__status_error">{{ errorText() }}</p>
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/app/components/auto-configure/auto-configure.component.html`
around lines 4 - 39, The template uses Angular's legacy structural directives
(*ngIf, *ngFor and "else errorBlock" plus trackBy: trackByIndex); replace them
with the project's built-in control flow: use `@if`(done()) / `@if`(!done()) blocks
instead of *ngIf(done()) and inline the error branch with `@else` rather than a
separate <ng-template>, and convert the status list to `@for`="let m of
messages(); let last = last" (or the project's `@for` syntax) instead of *ngFor
and remove the "trackBy: trackByIndex" usage (drop trackBy or implement
equivalent in the `@for` if supported). Update all occurrences referencing done(),
hasSummary(), tablesConfigured(), widgetsCount(), settingsCount(), messages(),
and errorText() to the new `@if/`@for constructs and ensure class bindings
([class.xxx]) remain applied to the items.

</div>
<div class="card__footer">
<p class="card__footer-text">You can start working — setup will continue in the background.</p>
Expand Down
Loading
Loading