fix(console): refine onboarding styles and behavior, i18n (#5616)

* fix: console onboarding styles and behavior, i18n

* action-row classes

* fix: only consider real user events in the onboarding view (#5617)

* fix(onboarding): only human events

* explicitly exclude system api events

---------

Co-authored-by: Max Peintner <[email protected]>

---------

Co-authored-by: Elio Bischof <[email protected]>
Co-authored-by: Elio Bischof <[email protected]>
This commit is contained in:
Max Peintner
2023-04-06 12:02:46 +00:00
committed by GitHub
co-authored by Elio Bischof Elio Bischof
parent 698f46fe6a
commit 2ee7b9c381
18 changed files with 364 additions and 100 deletions
@@ -189,6 +189,7 @@
border: none;
padding: 2px;
border-radius: 50vw;
cursor: pointer;
&:hover {
background: if($is-dark-theme, #ffffff, $primary-color);
@@ -72,6 +72,7 @@
margin-right: 1rem;
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2);
i {
font-size: 1rem;
@@ -1,7 +1,5 @@
<div class="onboarding-header">
<h1 class="title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
<p class="desc cnsl-secondary-text">{{ 'ONBOARDING.DESCRIPTION' | translate }}</p>
<h2 class="desc">{{ 'ONBOARDING.DESCRIPTION' | translate }}</h2>
<ng-container *ngIf="!adminService.hideOnboarding && (adminService.progressAllDone | async) === false">
<div class="onboarding-progress-bar-wrapper">
@@ -38,16 +36,34 @@
</div>
<div class="action-content">
<div class="text-block">
<span class="name">{{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }}</span>
<span class="cnsl-secondary-text description">{{
'ONBOARDING.EVENTS.' + action[0] + '.description' | translate
}}</span>
<div class="action-content-row">
<div
class="icon-wrapper"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? action[1].darkcolor + 50 : action[1].lightcolor + 50
}"
>
<div
class="inner"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? action[1].darkcolor : action[1].lightcolor,
color: (themeService.isDarkTheme | async) ? action[1].lightcolor : action[1].darkcolor
}"
>
<i class="{{ action[1].iconClasses }}"></i>
</div>
</div>
<div class="text-block">
<span class="name">{{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }}</span>
<span class="cnsl-secondary-text description">{{
'ONBOARDING.EVENTS.' + action[0] + '.description' | translate
}}</span>
</div>
</div>
<span class="fill-space"></span>
<div class="action-row">
<span>{{ 'ACTIONS.SETUP' | translate }}</span>
<span>{{ 'ONBOARDING.EVENTS.' + action[0] + '.action' | translate }}</span>
<mat-icon class="icon">keyboard_arrow_right</mat-icon>
</div>
</div>
@@ -23,14 +23,10 @@
flex-direction: column;
margin-bottom: 2rem;
.title {
font-size: 2rem;
margin-bottom: 1rem;
}
.desc {
font-size: 14px;
font-size: 1.2rem;
margin-top: 0;
text-transform: uppercase;
}
.onboarding-progress-bar-wrapper {
@@ -71,7 +67,6 @@
.action-card {
position: relative;
z-index: 100;
margin: 1rem;
flex-basis: 270px;
text-decoration: none;
@@ -91,18 +86,46 @@
height: 100%;
padding-right: 0.5rem;
.text-block {
.action-content-row {
display: flex;
flex-direction: column;
color: map-get($foreground, text);
padding-top: 1rem;
flex-direction: row;
align-items: flex-start;
.name {
margin-bottom: 1rem;
.icon-wrapper {
display: flex;
justify-content: center;
align-items: center;
padding: 0.5rem;
border-radius: 50vw;
flex-shrink: 0;
margin-right: 1rem;
margin-top: 1rem;
.inner {
border-radius: 50vw;
height: 40px;
width: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.75rem;
}
}
.description {
font-size: 14px;
.text-block {
display: flex;
flex-direction: column;
color: map-get($foreground, text);
padding-top: 1rem;
flex: 1;
.name {
margin-bottom: 1rem;
}
.description {
font-size: 14px;
}
}
}
@@ -111,6 +134,12 @@
}
}
&.done {
.action-content {
opacity: 0.5;
}
}
.state-circle {
position: absolute;
top: 0;
@@ -126,6 +155,7 @@
margin-right: 1rem;
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2);
i {
font-size: 1rem;
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { AdminService } from 'src/app/services/admin.service';
import { ThemeService } from 'src/app/services/theme.service';
import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding';
@Component({
@@ -10,7 +11,7 @@ import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding';
export class OnboardingComponent {
public actions = this.adminService.progressEvents;
constructor(public adminService: AdminService) {
constructor(public adminService: AdminService, public themeService: ThemeService) {
this.adminService.loadEvents.next(ONBOARDING_EVENTS);
}
}
@@ -28,6 +28,11 @@
display: flex;
align-items: center;
h2 {
text-transform: uppercase;
font-size: 1.2rem;
}
.shortcut-btn {
margin-left: 0.5rem;
}
+53 -9
View File
@@ -1,4 +1,6 @@
<div class="max-width-container">
<h1 class="home-title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
<div class="home-wrapper enlarged-container">
<ng-container *ngIf="['iam.read$'] | hasRole | async; else defaultHome">
<cnsl-onboarding></cnsl-onboarding>
@@ -10,24 +12,66 @@
<p class="disclaimer cnsl-secondary-text">{{ 'HOME.DISCLAIMER' | translate }}</p>
<span class="fill-space"></span>
<h2 class="desc">{{ 'ONBOARDING.MOREDESCRIPTION' | translate }}</h2>
<div class="home-grid-container">
<a href="https://zitadel.com/docs" target="_blank" rel="noreferrer" class="grid-item blue">
<div class="grid-item-avatar blue">
<i class="icon las la-file-alt"></i>
<a href="https://zitadel.com/docs" target="_blank" rel="noreferrer" class="grid-item">
<div
class="icon-wrapper"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? bluedark + 50 : bluelight + 50
}"
>
<div
class="inner"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? bluedark : bluelight,
color: (themeService.isDarkTheme | async) ? bluelight : bluedark
}"
>
<i class="las la-file-alt"></i>
</div>
</div>
<span>{{ 'HOME.DOCUMENTATION.TITLE' | translate }}</span>
</a>
<a href="https://zitadel.com/docs/guides/start/quickstart" target="_blank" rel="noreferrer" class="grid-item green">
<div class="grid-item-avatar green">
<i class="icon las la-play"></i>
<a href="https://zitadel.com/docs/guides/start/quickstart" target="_blank" rel="noreferrer" class="grid-item">
<div
class="icon-wrapper"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? greendark + 50 : greenlight + 50
}"
>
<div
class="inner"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? greendark : greenlight,
color: (themeService.isDarkTheme | async) ? greenlight : greendark
}"
>
<i class="las la-play"></i>
</div>
</div>
<span>{{ 'HOME.GETSTARTED.TITLE' | translate }}</span>
</a>
<a href="https://zitadel.com/docs/examples/introduction" target="_blank" rel="noreferrer" class="grid-item green">
<div class="grid-item-avatar green">
<i class="icon las la-play"></i>
<a href="https://zitadel.com/docs/examples/introduction" target="_blank" rel="noreferrer" class="grid-item">
<div
class="icon-wrapper"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? cyandark + 50 : cyanlight + 50
}"
>
<div
class="inner"
[ngStyle]="{
background: (themeService.isDarkTheme | async) ? cyandark : cyanlight,
color: (themeService.isDarkTheme | async) ? cyanlight : cyandark
}"
>
<i class="las la-play"></i>
</div>
</div>
<span>{{ 'HOME.QUICKSTARTS.TITLE' | translate }}</span>
</a>
+33 -11
View File
@@ -18,6 +18,11 @@
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000);
.home-title {
font-size: 2rem;
margin-bottom: 1rem;
}
.home-wrapper {
position: relative;
display: flex;
@@ -34,6 +39,12 @@
}
}
.desc {
font-size: 1.2rem;
margin-top: 0;
text-transform: uppercase;
}
.home-grid-container {
display: grid;
grid-template-columns: 1fr;
@@ -58,24 +69,15 @@
font-size: 14px;
background-color: $card-background-color;
border: 1px solid $border-color;
border-radius: 1rem;
border-radius: 0.5rem;
margin: 0;
text-decoration: none;
color: white;
box-shadow: 0 0 15px #0000001a;
color: inherit;
&.edit-state {
cursor: move;
}
&.green {
background: linear-gradient(40deg, #059669 30%, #047857);
}
&.blue {
background: linear-gradient(40deg, #3b82f6 30%, #4f46e5);
}
.grid-item-avatar {
height: 40px;
width: 40px;
@@ -118,6 +120,26 @@
color: white;
}
}
.icon-wrapper {
display: flex;
justify-content: center;
align-items: center;
padding: 0.5rem;
border-radius: 50vw;
flex-shrink: 0;
margin-right: 1rem;
.inner {
border-radius: 50vw;
height: 40px;
width: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.75rem;
}
}
}
}
+12 -1
View File
@@ -1,6 +1,8 @@
import { Component } from '@angular/core';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ThemeService } from 'src/app/services/theme.service';
import { COLORS } from 'src/app/utils/color';
@Component({
selector: 'cnsl-home',
@@ -8,9 +10,18 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
public greendark: string = COLORS[6][700];
public greenlight = COLORS[6][200];
public cyandark: string = COLORS[7][700];
public cyanlight = COLORS[7][200];
public bluedark: string = COLORS[9][700];
public bluelight = COLORS[9][200];
public dark: boolean = true;
constructor(public authService: GrpcAuthService, breadcrumbService: BreadcrumbService) {
constructor(public authService: GrpcAuthService, breadcrumbService: BreadcrumbService, public themeService: ThemeService) {
const bread: Breadcrumb = {
type: BreadcrumbType.ORG,
routerLink: ['/org'],
+31 -4
View File
@@ -267,9 +267,20 @@ export interface OnboardingActions {
oneof: string[];
link: string | string[];
fragment?: string | undefined;
iconClasses?: string;
darkcolor: string;
lightcolor: string;
}
type OnboardingEvent = { order: number; link: string; fragment: string | undefined; event: Event.AsObject | undefined };
type OnboardingEvent = {
order: number;
link: string;
fragment: string | undefined;
event: Event.AsObject | undefined;
iconClasses?: string;
darkcolor: string;
lightcolor: string;
};
type OnboardingEventEntries = Array<[string, OnboardingEvent]> | [];
@Injectable({
@@ -286,14 +297,30 @@ export class AdminService {
const eventsReq = new ListEventsRequest().setAsc(true).setEventTypesList(searchForTypes).setAsc(false);
return from(this.listEvents(eventsReq)).pipe(
map((events) => {
const el = events.toObject().eventsList.filter((e) => e.editor?.service !== 'System-API');
const el = events.toObject().eventsList.filter((e) => e.editor?.service !== 'System-API' && e.editor?.userId);
let obj: { [type: string]: OnboardingEvent } = {};
actions.map((action) => {
const filtered = el.filter((event) => event.type?.type && action.oneof.includes(event.type.type));
(obj as any)[action.eventType] = filtered.length
? { order: action.order, link: action.link, fragment: action.fragment, event: filtered[0] }
: { order: action.order, link: action.link, fragment: action.fragment, event: undefined };
? {
order: action.order,
link: action.link,
fragment: action.fragment,
event: filtered[0],
iconClasses: action.iconClasses,
darkcolor: action.darkcolor,
lightcolor: action.lightcolor,
}
: {
order: action.order,
link: action.link,
fragment: action.fragment,
event: undefined,
iconClasses: action.iconClasses,
darkcolor: action.darkcolor,
lightcolor: action.lightcolor,
};
});
const toArray = Object.entries(obj).sort(([key0, a], [key1, b]) => a.order - b.order);
+61 -4
View File
@@ -1,16 +1,70 @@
import { OnboardingActions } from '../services/admin.service';
import { COLORS } from './color';
const reddark: string = COLORS[0][700];
const redlight = COLORS[0][200];
const yellowdark: string = COLORS[3][700];
const yellowlight = COLORS[3][200];
const greendark: string = COLORS[6][700];
const greenlight = COLORS[6][200];
const bluedark: string = COLORS[9][700];
const bluelight = COLORS[9][200];
const purpledark: string = COLORS[12][700];
const purplelight = COLORS[12][200];
const pinkdark: string = COLORS[15][700];
const pinklight = COLORS[15][200];
export const ONBOARDING_EVENTS: OnboardingActions[] = [
{ order: 0, eventType: 'project.added', oneof: ['project.added'], link: ['/projects/create'] },
{ order: 1, eventType: 'project.application.added', oneof: ['project.application.added'], link: ['/projects/app-create'] },
{ order: 2, eventType: 'user.human.added', oneof: ['user.human.added'], link: ['/users/create'] },
{ order: 3, eventType: 'user.grant.added', oneof: ['user.grant.added'], link: ['/grant-create'] },
{
order: 0,
eventType: 'project.added',
oneof: ['project.added'],
link: ['/projects/create'],
iconClasses: 'las la-database',
darkcolor: greendark,
lightcolor: greenlight,
},
{
order: 1,
eventType: 'project.application.added',
oneof: ['project.application.added'],
link: ['/projects/app-create'],
iconClasses: 'lab la-openid',
darkcolor: purpledark,
lightcolor: purplelight,
},
{
order: 2,
eventType: 'user.human.added',
oneof: ['user.human.added'],
link: ['/users/create'],
iconClasses: 'las la-user',
darkcolor: bluedark,
lightcolor: bluelight,
},
{
order: 3,
eventType: 'user.grant.added',
oneof: ['user.grant.added'],
link: ['/grant-create'],
iconClasses: 'las la-shield-alt',
darkcolor: reddark,
lightcolor: redlight,
},
{
order: 4,
eventType: 'instance.policy.label.added',
oneof: ['instance.policy.label.added', 'instance.policy.label.changed'],
link: ['/settings'],
fragment: 'branding',
iconClasses: 'las la-swatchbook',
darkcolor: pinkdark,
lightcolor: pinklight,
},
{
order: 5,
@@ -18,5 +72,8 @@ export const ONBOARDING_EVENTS: OnboardingActions[] = [
oneof: ['instance.smtp.config.added', 'instance.smtp.config.changed'],
link: ['/settings'],
fragment: 'notifications',
iconClasses: 'las la-envelope',
darkcolor: yellowdark,
lightcolor: yellowlight,
},
];