Browse Source

feat(iot-hub): add verified creator badge, sticky meta bar in detail dialog

Add verified field to CreatorView and creatorVerified to MpItemVersionView.
Show verified badge (32px teal icon + tooltip) on creator profile page.
Show 16px teal verified icon on item cards when creator is verified.
Make dlg-meta-bar always rendered and sticky in item detail dialog.
pull/15508/head
Igor Kulikov 6 months ago
parent
commit
daed8abf90
  1. 12
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.html
  2. 4
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.scss
  3. 53
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html
  4. 7
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.scss
  5. 9
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-creator-profile.component.html
  6. 15
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-creator-profile.component.scss
  7. 1
      ui-ngx/src/app/shared/models/iot-hub/iot-hub-creator.models.ts
  8. 1
      ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts
  9. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

12
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.html

@ -28,7 +28,11 @@
<h3 class="tb-iot-hub-compact-name">{{ item.name }}</h3> <h3 class="tb-iot-hub-compact-name">{{ item.name }}</h3>
<div class="tb-iot-hub-compact-meta"> <div class="tb-iot-hub-compact-meta">
@if (showCreator) { @if (showCreator) {
<tb-icon class="tb-iot-hub-compact-author-icon">person</tb-icon> @if (item.creatorVerified) {
<mat-icon class="tb-iot-hub-compact-author-icon tb-iot-hub-verified-icon">verified</mat-icon>
} @else {
<tb-icon class="tb-iot-hub-compact-author-icon">person</tb-icon>
}
<span class="tb-iot-hub-compact-creator" [class.clickable]="mode !== 'add'" (click)="mode !== 'add' && onCreatorClick($event)">{{ item.creatorDisplayName }}</span> <span class="tb-iot-hub-compact-creator" [class.clickable]="mode !== 'add'" (click)="mode !== 'add' && onCreatorClick($event)">{{ item.creatorDisplayName }}</span>
<span class="tb-iot-hub-compact-dot">&middot;</span> <span class="tb-iot-hub-compact-dot">&middot;</span>
} }
@ -89,7 +93,11 @@
<h3 class="tb-iot-hub-card-name">{{ item.name }}</h3> <h3 class="tb-iot-hub-card-name">{{ item.name }}</h3>
<div class="tb-iot-hub-card-footer"> <div class="tb-iot-hub-card-footer">
@if (showCreator) { @if (showCreator) {
<tb-icon class="tb-iot-hub-card-author-icon">person</tb-icon> @if (item.creatorVerified) {
<mat-icon class="tb-iot-hub-card-author-icon tb-iot-hub-verified-icon">verified</mat-icon>
} @else {
<tb-icon class="tb-iot-hub-card-author-icon">person</tb-icon>
}
<span class="tb-iot-hub-card-creator" [class.clickable]="mode !== 'add'" (click)="mode !== 'add' && onCreatorClick($event)">{{ item.creatorDisplayName }}</span> <span class="tb-iot-hub-card-creator" [class.clickable]="mode !== 'add'" (click)="mode !== 'add' && onCreatorClick($event)">{{ item.creatorDisplayName }}</span>
<span class="tb-iot-hub-card-dot">&middot;</span> <span class="tb-iot-hub-card-dot">&middot;</span>
} }

4
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.scss

@ -177,6 +177,10 @@
flex-shrink: 0; flex-shrink: 0;
} }
.tb-iot-hub-verified-icon {
color: #00695c;
}
.tb-iot-hub-compact-creator { .tb-iot-hub-compact-creator {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

53
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html

@ -251,10 +251,10 @@
</div> </div>
<!-- Categories & Use Cases / Device metadata --> <!-- Categories & Use Cases / Device metadata -->
@if (item.type === ItemType.DEVICE) { <div class="dlg-meta-bar">
@if (item.dataDescriptor?.hardwareType || item.dataDescriptor?.connectivity?.length || item.useCases?.length) { <div class="dlg-divider"></div>
<div class="dlg-divider"></div> <div class="dlg-meta">
<div class="dlg-meta"> @if (item.type === ItemType.DEVICE) {
@if (item.dataDescriptor?.hardwareType) { @if (item.dataDescriptor?.hardwareType) {
<div class="dlg-meta-group"> <div class="dlg-meta-group">
<span class="dlg-meta-label">{{ 'iot-hub.hardware-type' | translate }}</span> <span class="dlg-meta-label">{{ 'iot-hub.hardware-type' | translate }}</span>
@ -273,22 +273,7 @@
</div> </div>
</div> </div>
} }
@if (item.useCases?.length) { } @else {
<div class="dlg-meta-group">
<span class="dlg-meta-label">{{ 'iot-hub.use-cases' | translate }}</span>
<div class="dlg-meta-chips">
@for (uc of item.useCases; track uc) {
<span class="dlg-chip dlg-chip-usecase">{{ uc }}</span>
}
</div>
</div>
}
</div>
}
} @else {
@if (item.categories?.length || item.useCases?.length) {
<div class="dlg-divider"></div>
<div class="dlg-meta">
@if (item.categories?.length) { @if (item.categories?.length) {
<div class="dlg-meta-group"> <div class="dlg-meta-group">
<span class="dlg-meta-label">{{ 'iot-hub.category' | translate }}</span> <span class="dlg-meta-label">{{ 'iot-hub.category' | translate }}</span>
@ -299,33 +284,33 @@
</div> </div>
</div> </div>
} }
@if (item.useCases?.length) { }
<div class="dlg-meta-group"> @if (item.useCases?.length) {
<span class="dlg-meta-label">{{ 'iot-hub.use-cases' | translate }}</span> <div class="dlg-meta-group">
<div class="dlg-meta-chips"> <span class="dlg-meta-label">{{ 'iot-hub.use-cases' | translate }}</span>
@for (uc of item.useCases; track uc) { <div class="dlg-meta-chips">
<span class="dlg-chip dlg-chip-usecase">{{ uc }}</span> @for (uc of item.useCases; track uc) {
} <span class="dlg-chip dlg-chip-usecase">{{ uc }}</span>
</div> }
</div> </div>
} </div>
</div> }
} </div>
} <div class="dlg-divider"></div>
</div>
<!-- Rule Chain Nodes --> <!-- Rule Chain Nodes -->
@if (item.type === ItemType.RULE_CHAIN && getNodes().length) { @if (item.type === ItemType.RULE_CHAIN && getNodes().length) {
<div class="dlg-divider"></div>
<div class="dlg-nodes"> <div class="dlg-nodes">
@for (node of getNodes(); track node.name) { @for (node of getNodes(); track node.name) {
<span class="dlg-node-chip" [ngClass]="'dlg-node-' + node.type.toLowerCase()">{{ node.name }}</span> <span class="dlg-node-chip" [ngClass]="'dlg-node-' + node.type.toLowerCase()">{{ node.name }}</span>
} }
</div> </div>
<div class="dlg-divider"></div>
} }
<!-- Readme / Changelog --> <!-- Readme / Changelog -->
@if (readmeContent || item.changelog) { @if (readmeContent || item.changelog) {
<div class="dlg-divider"></div>
<div class="dlg-readme"> <div class="dlg-readme">
@if (readmeContent) { @if (readmeContent) {
<tb-markdown [data]="readmeContent"></tb-markdown> <tb-markdown [data]="readmeContent"></tb-markdown>

7
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.scss

@ -263,6 +263,13 @@
background: rgba(0, 0, 0, 0.12); background: rgba(0, 0, 0, 0.12);
} }
.dlg-meta-bar {
position: sticky;
top: 0;
z-index: 1;
background: white;
}
// Meta section (categories + use cases) — Design: px-24, py-16, gap-24, flex row // Meta section (categories + use cases) — Design: px-24, py-16, gap-24, flex row
.dlg-meta { .dlg-meta {
display: flex; display: flex;

9
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-creator-profile.component.html

@ -32,7 +32,14 @@
<mat-icon>person</mat-icon> <mat-icon>person</mat-icon>
} }
</div> </div>
<h2 class="tb-creator-name">{{ creator.displayName }}</h2> <div class="tb-creator-name-row">
<h2 class="tb-creator-name">{{ creator.displayName }}</h2>
@if (creator.verified) {
<mat-icon class="tb-creator-verified-badge"
[matTooltip]="'iot-hub.verified-creator' | translate"
matTooltipPosition="above">verified</mat-icon>
}
</div>
@if (creator.description) { @if (creator.description) {
<p class="tb-creator-desc">{{ creator.description }}</p> <p class="tb-creator-desc">{{ creator.description }}</p>
} }

15
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-creator-profile.component.scss

@ -79,6 +79,12 @@
} }
// Name // Name
.tb-creator-name-row {
display: flex;
align-items: center;
gap: 8px;
}
.tb-creator-name { .tb-creator-name {
font-size: 28px; font-size: 28px;
font-weight: 500; font-weight: 500;
@ -88,6 +94,15 @@
margin: 0; margin: 0;
} }
.tb-creator-verified-badge {
font-size: 32px;
width: 32px;
height: 32px;
color: #00695c;
flex-shrink: 0;
cursor: default;
}
// Description // Description
.tb-creator-desc { .tb-creator-desc {
font-size: 16px; font-size: 16px;

1
ui-ngx/src/app/shared/models/iot-hub/iot-hub-creator.models.ts

@ -26,4 +26,5 @@ export interface CreatorView {
linkedinUrl: string; linkedinUrl: string;
twitterUrl: string; twitterUrl: string;
publishedCount: number; publishedCount: number;
verified: boolean;
} }

1
ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts

@ -106,6 +106,7 @@ export interface MpItemVersionView {
creatorContactEmail: string; creatorContactEmail: string;
creatorDescription: string; creatorDescription: string;
creatorAvatarUrl: string; creatorAvatarUrl: string;
creatorVerified: boolean;
installCount: number; installCount: number;
totalInstallCount: number; totalInstallCount: number;
resources: MpItemVersionResource[]; resources: MpItemVersionResource[];

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3774,6 +3774,7 @@
"no-items-found-text": "Try adjusting your search or filters.", "no-items-found-text": "Try adjusting your search or filters.",
"become-a-creator": "Become a creator", "become-a-creator": "Become a creator",
"back-to-iot-hub": "Back to IoT Hub", "back-to-iot-hub": "Back to IoT Hub",
"verified-creator": "Verified Creator",
"published-items": "Published items", "published-items": "Published items",
"by-creator": "by {{name}}", "by-creator": "by {{name}}",
"installs": "installs", "installs": "installs",

Loading…
Cancel
Save