mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-29 18:42:15 +00:00
feat(console): integrate app (#7417)
* docs, frameworks view * project select, integrate app page * fix search project autocomplete * framework autocomplete * framwork select component, integrate, mapping to oidc config * param * fix route handler * setname projectid context * app-create page without context * show description of app type, info section * redirects section * updatevalue observable * fix redirect uris section * i18n * setup config * backbutton behavior, cleanup * cleanup * lint * allow other framework jump off * dev mode warning * navigate to project * rm import * i18n, guide link * edit name dialog * show warning for duplicate name
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
<form>
|
||||
<cnsl-form-field class="full-width">
|
||||
<cnsl-label>{{ 'QUICKSTART.FRAMEWORK' | translate }}</cnsl-label>
|
||||
<input cnslInput type="text" placeholder="" #nameInput [formControl]="myControl" [matAutocomplete]="auto" />
|
||||
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
|
||||
<mat-option *ngIf="isLoading()" class="is-loading">
|
||||
<mat-spinner diameter="30"></mat-spinner>
|
||||
</mat-option>
|
||||
<mat-option *ngFor="let framework of filteredOptions | async" [value]="framework.id">
|
||||
<div class="framework-option">
|
||||
<div class="framework-option-column">
|
||||
<div class="img-wrapper">
|
||||
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
|
||||
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
|
||||
<span>{{ framework.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</mat-option>
|
||||
<mat-option *ngIf="withCustom" [value]="'other'">
|
||||
<div class="framework-option">
|
||||
<div class="framework-option-column">
|
||||
<div class="img-wrapper"></div>
|
||||
<span class="fill-space"></span>
|
||||
|
||||
<span>{{ 'QUICKSTART.FRAMEWORK_OTHER' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</cnsl-form-field>
|
||||
</form>
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
@mixin framework-autocomplete-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$accent: map-get($theme, accent);
|
||||
$primary-color: map-get($primary, 500);
|
||||
|
||||
$warn-color: map-get($warn, 500);
|
||||
$accent-color: map-get($accent, 500);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$back: map-get($background, background);
|
||||
|
||||
$list-background-color: map-get($background, 300);
|
||||
$card-background-color: map-get($background, cards);
|
||||
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
|
||||
$border-selected-color: if($is-dark-theme, #fff, #000);
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.framework-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.framework-option-column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.img-wrapper {
|
||||
width: 50px;
|
||||
margin-right: 1rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 30px;
|
||||
max-height: 30px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.dark-only {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
||||
.light-only {
|
||||
display: if($is-dark-theme, none, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FrameworkAutocompleteComponent } from './framework-autocomplete.component';
|
||||
|
||||
describe('FrameworkAutocompleteComponent', () => {
|
||||
let component: FrameworkAutocompleteComponent;
|
||||
let fixture: ComponentFixture<FrameworkAutocompleteComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FrameworkAutocompleteComponent],
|
||||
});
|
||||
fixture = TestBed.createComponent(FrameworkAutocompleteComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, signal } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { InputModule } from 'src/app/modules/input/input.module';
|
||||
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Observable, map, of, startWith, switchMap, tap } from 'rxjs';
|
||||
import { Framework } from '../quickstart/quickstart.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'cnsl-framework-autocomplete',
|
||||
templateUrl: './framework-autocomplete.component.html',
|
||||
styleUrls: ['./framework-autocomplete.component.scss'],
|
||||
imports: [
|
||||
TranslateModule,
|
||||
RouterModule,
|
||||
MatSelectModule,
|
||||
MatAutocompleteModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
InputModule,
|
||||
],
|
||||
})
|
||||
export class FrameworkAutocompleteComponent implements OnInit {
|
||||
public isLoading = signal(false);
|
||||
@Input() public frameworkId?: string;
|
||||
@Input() public frameworks: Framework[] = [];
|
||||
@Input() public withCustom: boolean = false;
|
||||
public myControl: FormControl = new FormControl();
|
||||
@Output() public selectionChanged: EventEmitter<string> = new EventEmitter();
|
||||
public filteredOptions: Observable<Framework[]> = of([]);
|
||||
|
||||
constructor() {}
|
||||
|
||||
public ngOnInit() {
|
||||
this.filteredOptions = this.myControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map((value) => {
|
||||
return this._filter(value || '');
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private _filter(value: string): Framework[] {
|
||||
const filterValue = value.toLowerCase();
|
||||
return this.frameworks
|
||||
.filter((option) => option.id)
|
||||
.filter((option) => option.title.toLowerCase().includes(filterValue));
|
||||
}
|
||||
|
||||
public selected(event: MatAutocompleteSelectedEvent): void {
|
||||
this.selectionChanged.emit(event.option.value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<h2 mat-dialog-title>{{ 'QUICKSTART.DIALOG.CHANGE.TITLE' | translate }}</h2>
|
||||
<mat-dialog-content>
|
||||
{{ 'QUICKSTART.DIALOG.CHANGE.DESCRIPTION' | translate }}
|
||||
<div class="framework-change-block">
|
||||
<cnsl-framework-autocomplete
|
||||
[frameworkId]="data.framework.id"
|
||||
[frameworks]="data.frameworks"
|
||||
(selectionChanged)="findFramework($event)"
|
||||
></cnsl-framework-autocomplete>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<div>
|
||||
<mat-dialog-actions class="actions">
|
||||
<button mat-stroked-button mat-dialog-close>{{ 'ACTIONS.CANCEL' | translate }}</button>
|
||||
<button color="primary" mat-raised-button (click)="close()" cdkFocusInitial>{{ 'ACTIONS.CHANGE' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
.framework-change-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Component, Inject, signal } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogModule,
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
} from '@angular/material/dialog';
|
||||
import { FrameworkAutocompleteComponent } from '../framework-autocomplete/framework-autocomplete.component';
|
||||
import { Framework } from '../quickstart/quickstart.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-framework-change-dialog',
|
||||
templateUrl: './framework-change-dialog.component.html',
|
||||
styleUrls: ['./framework-change-dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MatButtonModule, MatDialogModule, TranslateModule, FrameworkAutocompleteComponent],
|
||||
})
|
||||
export class FrameworkChangeDialogComponent {
|
||||
public framework = signal<Framework | undefined>(undefined);
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<FrameworkChangeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
) {
|
||||
this.framework.set(data.framework);
|
||||
}
|
||||
|
||||
public findFramework(id: string) {
|
||||
const temp = this.data.frameworks.find((f: Framework) => f.id === id);
|
||||
this.framework.set(temp);
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.dialogRef.close(this.framework());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="framework-change-wrapper">
|
||||
<div class="framework-card-wrapper">
|
||||
<div class="framework-card card" *ngIf="framework | async as framework">
|
||||
<div [routerLink]="['/app-create']" [queryParams]="{ id: framework.docsLink }" class="">
|
||||
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
|
||||
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
|
||||
</div>
|
||||
<span>{{ framework.title }}</span>
|
||||
</div>
|
||||
<button (click)="openDialog()" mat-stroked-button>
|
||||
{{ 'ACTIONS.CHANGE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,82 @@
|
||||
@mixin framework-change-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$accent: map-get($theme, accent);
|
||||
$primary-color: map-get($primary, 500);
|
||||
|
||||
$warn-color: map-get($warn, 500);
|
||||
$accent-color: map-get($accent, 500);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$back: map-get($background, background);
|
||||
|
||||
$list-background-color: map-get($background, 300);
|
||||
$card-background-color: map-get($background, cards);
|
||||
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
|
||||
$border-selected-color: if($is-dark-theme, #fff, #000);
|
||||
|
||||
.framework-change-wrapper {
|
||||
.framework-card-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
.framework-card {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
border-radius: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.1s ease-in;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
// background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
|
||||
// box-shadow: 0 0 3px #0000001a;
|
||||
border: 1px solid rgba(#8795a1, 0.2);
|
||||
padding: 0 0.5rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 40px;
|
||||
max-height: 40px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.dark-only {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
||||
.light-only {
|
||||
display: if($is-dark-theme, none, block);
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0.5rem;
|
||||
text-align: center;
|
||||
color: map-get($foreground, text);
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
margin-bottom: 0.5rem;
|
||||
color: map-get($primary, 400);
|
||||
|
||||
.icon {
|
||||
margin-left: 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FrameworkChangeComponent } from './framework-change.component';
|
||||
|
||||
describe('FrameworkChangeComponent', () => {
|
||||
let component: FrameworkChangeComponent;
|
||||
let fixture: ComponentFixture<FrameworkChangeComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FrameworkChangeComponent],
|
||||
});
|
||||
fixture = TestBed.createComponent(FrameworkChangeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output, effect, signal } from '@angular/core';
|
||||
import { ActivatedRoute, Params, RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import frameworkDefinition from '../../../../../docs/frameworks.json';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
|
||||
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
|
||||
import { FrameworkAutocompleteComponent } from '../framework-autocomplete/framework-autocomplete.component';
|
||||
import { Framework } from '../quickstart/quickstart.component';
|
||||
import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
|
||||
import {
|
||||
MatDialog,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogModule,
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
} from '@angular/material/dialog';
|
||||
import { FrameworkChangeDialogComponent } from './framework-change-dialog.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'cnsl-framework-change',
|
||||
templateUrl: './framework-change.component.html',
|
||||
styleUrls: ['./framework-change.component.scss'],
|
||||
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule, FrameworkAutocompleteComponent],
|
||||
})
|
||||
export class FrameworkChangeComponent implements OnInit, OnDestroy {
|
||||
private destroy$: Subject<void> = new Subject();
|
||||
public framework: BehaviorSubject<Framework | undefined> = new BehaviorSubject<Framework | undefined>(undefined);
|
||||
public showFrameworkAutocomplete = signal<boolean>(false);
|
||||
@Output() public frameworkChanged: EventEmitter<Framework> = new EventEmitter();
|
||||
public frameworks: Framework[] = frameworkDefinition.map((f) => {
|
||||
return {
|
||||
...f,
|
||||
fragment: '',
|
||||
imgSrcDark: `assets${f.imgSrcDark}`,
|
||||
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
|
||||
};
|
||||
});
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private dialog: MatDialog,
|
||||
) {
|
||||
this.framework.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.frameworkChanged.emit(value);
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
|
||||
const { framework } = params;
|
||||
if (framework) {
|
||||
this.findFramework(framework);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
public findFramework(id: string) {
|
||||
const temp = this.frameworks.find((f) => f.id === id);
|
||||
this.framework.next(temp);
|
||||
this.frameworkChanged.emit(temp);
|
||||
}
|
||||
|
||||
public openDialog(): void {
|
||||
const ref = this.dialog.open(FrameworkChangeDialogComponent, {
|
||||
width: '400px',
|
||||
data: {
|
||||
framework: this.framework.value,
|
||||
frameworks: this.frameworks,
|
||||
},
|
||||
});
|
||||
|
||||
ref.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.framework.next(resp);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<div class="configuration-wrapper">
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.NAME' | translate }}
|
||||
</span>
|
||||
<span class="right name">
|
||||
<span>{{ name }}</span>
|
||||
<button (click)="changeName.emit()" mat-icon-button><i class="las la-pen"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.TYPE' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
{{ 'APP.OIDC.APPTYPE.' + configuration.appType | translate }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.GRANT' | translate }}
|
||||
</span>
|
||||
<span class="right" *ngIf="configuration.grantTypesList && configuration.grantTypesList.length > 0">
|
||||
[<span *ngFor="let element of configuration.grantTypesList ?? []; index as i">
|
||||
{{ 'APP.OIDC.GRANT.' + element | translate }}
|
||||
{{ i < configuration.grantTypesList.length - 1 ? ', ' : '' }} </span
|
||||
>]
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.OIDC.RESPONSETYPE' | translate }}
|
||||
</span>
|
||||
<span class="right" *ngIf="configuration.responseTypesList && configuration.responseTypesList.length > 0">
|
||||
[<span *ngFor="let element of configuration.responseTypesList ?? []; index as i">
|
||||
{{ 'APP.OIDC.RESPONSE.' + element | translate }}
|
||||
{{ i < configuration.responseTypesList.length - 1 ? ', ' : '' }} </span
|
||||
>]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<span class="left cnsl-secondary-text">
|
||||
{{ 'APP.AUTHMETHOD' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span>
|
||||
{{ 'APP.OIDC.AUTHMETHOD.' + configuration.authMethodType | translate }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
.configuration-wrapper {
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.left,
|
||||
.right {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
margin-right: -0.5rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { QuickstartComponent } from './quickstart.component';
|
||||
|
||||
describe('QuickstartComponent', () => {
|
||||
let component: QuickstartComponent;
|
||||
let fixture: ComponentFixture<QuickstartComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [QuickstartComponent],
|
||||
});
|
||||
fixture = TestBed.createComponent(QuickstartComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import frameworkDefinition from '../../../../../docs/frameworks.json';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
|
||||
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
|
||||
import { AddOIDCAppRequest } from 'src/app/proto/generated/zitadel/management_pb';
|
||||
|
||||
export type FrameworkDefinition = {
|
||||
id?: FrameworkName | string;
|
||||
title: string;
|
||||
imgSrcDark: string;
|
||||
imgSrcLight?: string;
|
||||
docsLink: string;
|
||||
external?: boolean;
|
||||
};
|
||||
|
||||
export type Framework = FrameworkDefinition & {
|
||||
fragment: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'cnsl-oidc-app-configuration',
|
||||
templateUrl: './oidc-configuration.component.html',
|
||||
styleUrls: ['./oidc-configuration.component.scss'],
|
||||
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
|
||||
})
|
||||
export class OIDCConfigurationComponent {
|
||||
@Input() public name?: string;
|
||||
@Input() public configuration: AddOIDCAppRequest.AsObject = new AddOIDCAppRequest().toObject();
|
||||
@Output() public changeName: EventEmitter<string> = new EventEmitter();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<div class="quickstart-header">
|
||||
<div class="quickstart-left">
|
||||
<h2>{{ 'QUICKSTART.TITLE' | translate }}</h2>
|
||||
<p class="description">{{ 'QUICKSTART.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<div class="btn-wrapper">
|
||||
<a mat-raised-button color="primary" [routerLink]="['/projects', 'app-create']">{{
|
||||
'QUICKSTART.BTN_START' | translate
|
||||
}}</a>
|
||||
<a mat-stroked-button color="primary" href="https://zitadel.com/docs/sdk-examples/introduction" target="_blank">{{
|
||||
'QUICKSTART.BTN_LEARNMORE' | translate
|
||||
}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quickstart-card-wrapper">
|
||||
<ng-container *ngFor="let framework of frameworks.slice(0, 18)">
|
||||
<a [routerLink]="['/projects', 'app-create']" [queryParams]="{ framework: framework.id }" class="quickstart-card card">
|
||||
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" alt="{{ framework.title }}" />
|
||||
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" alt="{{ framework.title }}" />
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,121 @@
|
||||
@mixin quickstart-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$accent: map-get($theme, accent);
|
||||
$primary-color: map-get($primary, 500);
|
||||
|
||||
$warn-color: map-get($warn, 500);
|
||||
$accent-color: map-get($accent, 500);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$back: map-get($background, background);
|
||||
|
||||
$list-background-color: map-get($background, 300);
|
||||
$card-background-color: map-get($background, cards);
|
||||
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
|
||||
$border-selected-color: if($is-dark-theme, #fff, #000);
|
||||
|
||||
.quickstart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0 -2rem;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
gap: 5rem;
|
||||
justify-content: space-between;
|
||||
background-color: map-get($background, metadata-section);
|
||||
|
||||
.quickstart-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 400px;
|
||||
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.quickstart-card-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
grid-column-gap: 1rem;
|
||||
grid-row-gap: 1rem;
|
||||
grid-auto-columns: 0;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
|
||||
.quickstart-card {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.1s ease-in;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 3px #0000001a;
|
||||
border: 1px solid rgba(#8795a1, 0.2);
|
||||
color: var(--success);
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
border: 2px solid var(--success);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 40px;
|
||||
max-height: 40px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.dark-only {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
||||
.light-only {
|
||||
display: if($is-dark-theme, none, block);
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0.5rem;
|
||||
text-align: center;
|
||||
color: map-get($foreground, text);
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
margin-bottom: 0.5rem;
|
||||
color: map-get($primary, 400);
|
||||
|
||||
.icon {
|
||||
margin-left: 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { QuickstartComponent } from './quickstart.component';
|
||||
|
||||
describe('QuickstartComponent', () => {
|
||||
let component: QuickstartComponent;
|
||||
let fixture: ComponentFixture<QuickstartComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [QuickstartComponent],
|
||||
});
|
||||
fixture = TestBed.createComponent(QuickstartComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import frameworkDefinition from '../../../../../docs/frameworks.json';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
|
||||
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
|
||||
import { OIDC_CONFIGURATIONS } from 'src/app/utils/framework';
|
||||
|
||||
export type FrameworkDefinition = {
|
||||
id?: FrameworkName | string;
|
||||
title: string;
|
||||
description?: string;
|
||||
imgSrcDark: string;
|
||||
imgSrcLight?: string;
|
||||
docsLink: string;
|
||||
external?: boolean;
|
||||
};
|
||||
|
||||
export type Framework = FrameworkDefinition & {
|
||||
fragment: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'cnsl-quickstart',
|
||||
templateUrl: './quickstart.component.html',
|
||||
styleUrls: ['./quickstart.component.scss'],
|
||||
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
|
||||
})
|
||||
export class QuickstartComponent {
|
||||
public frameworks: FrameworkDefinition[] = frameworkDefinition
|
||||
.filter((f) => f.id && OIDC_CONFIGURATIONS[f.id])
|
||||
.map((f) => {
|
||||
return {
|
||||
...f,
|
||||
imgSrcDark: `assets${f.imgSrcDark}`,
|
||||
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user