mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-06 14:22:15 +00:00
feat(console): integrate frontend (#95)
* feat: console frontend * chore(dependabot): cycle and npm * chore: rename citadel to zitadel, remove generated files * chore: delete go files * chore(frontend): ci steps * chore: remove docker and envoy files * chore: remove docker file * chore: working dir * chore: run proto build * add console start * chore: restructure folders * chore: remove gui build * statikFs * generate proto for console * add statik import * import * chore: try statik * chore: path * chore: path * chore: script in root * chore: order build steps * chore: go get * chore: folder traversal * chore: non empty test file * chore: gitignore * chore: gitignore * chore: statik path * chore: switch to failing FE build * fix: build * fix: project-grant-test * fix: rm test * add statik.go * go mod tidy * chore: place test, seperate test from build * chore: lint all the world * chore: ci the world instead * chore: tune docker * chore: undo container test * chore: fix run * chore: docker build * chore: test docker build * chore: go build flags * finaly * fix caos_local * go mod Co-authored-by: Livio Amstutz <[email protected]> Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
co-authored by
Livio Amstutz
Max Peintner
parent
9e32740eb8
commit
92a294f5c8
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { OrgCreateComponent } from './org-create.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: OrgCreateComponent,
|
||||
data: { animation: 'DetailPage' },
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class OrgCreateRoutingModule { }
|
||||
@@ -0,0 +1,145 @@
|
||||
<div class="max-width-container container">
|
||||
<div class="abort-container">
|
||||
<button (click)="close()" mat-icon-button>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<span class="abort">{{ 'ORG.PAGES.CREATE' | translate }}</span><span class="abort-2">Step
|
||||
{{ currentCreateStep }} of
|
||||
{{ createSteps }}</span>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="currentCreateStep == 1">
|
||||
<h1>{{'ORG.PAGES.ORGDETAIL_TITLE' | translate}}</h1>
|
||||
<form [formGroup]="orgForm">
|
||||
<div class="content">
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'ORG_DETAIL.DETAIL.NAME' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" />
|
||||
</mat-form-field>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'ORG_DETAIL.DETAIL.DOMAIN' | translate }}</mat-label>
|
||||
<input matInput formControlName="domain" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div *ngIf="name?.touched" @openClose>
|
||||
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION' | translate }}</p>
|
||||
|
||||
<p>{{domain?.value}}/.well-known/caos-developer-domain-association.txt</p>
|
||||
|
||||
<div class="btn-container">
|
||||
<button color="primary" type="submit"
|
||||
mat-stroked-button>{{ 'ORG.PAGES.DOWNLOAD_FILE' | translate }}</button>
|
||||
|
||||
<button color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.VERIFY' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION_SKIP' | translate }}</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentCreateStep == createSteps">
|
||||
<h1>{{'ORG.PAGES.ORGDETAILUSER_TITLE' | translate}}</h1>
|
||||
|
||||
<div class="user">
|
||||
<form [formGroup]="userForm" (ngSubmit)="finish()" class="form">
|
||||
<div class="content">
|
||||
<p class="section">{{ 'USER.CREATE.NAMEANDEMAILSECTION' | translate }}</p>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PROFILE.EMAIL' | translate }}</mat-label>
|
||||
<input matInput formControlName="email" required />
|
||||
<mat-error *ngIf="email?.invalid && email?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</mat-label>
|
||||
<input matInput formControlName="firstName" required />
|
||||
<mat-error *ngIf="firstName?.invalid && firstName?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PROFILE.LASTNAME' | translate }}</mat-label>
|
||||
<input matInput formControlName="lastName" required />
|
||||
<mat-error *ngIf="lastName?.invalid && lastName?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</mat-label>
|
||||
<input matInput formControlName="nickName" />
|
||||
<mat-error *ngIf="nickName?.invalid && nickName?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PROFILE.DISPLAYNAME' | translate }}</mat-label>
|
||||
<input matInput formControlName="displayName" />
|
||||
<mat-error *ngIf="displayName?.invalid && displayName?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<p class="section">{{ 'USER.CREATE.GENDERLANGSECTION' | translate }}</p>
|
||||
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PROFILE.GENDER' | translate }}</mat-label>
|
||||
<mat-select formControlName="gender">
|
||||
<mat-option *ngFor="let gender of genders" [value]="gender">
|
||||
{{ 'GENDERS.'+gender | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="gender?.invalid && gender?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</mat-label>
|
||||
<mat-select formControlName="preferredLanguage">
|
||||
<mat-option *ngFor="let language of languages" [value]="language">
|
||||
{{ 'LANGUAGES.'+language | translate }}
|
||||
</mat-option>
|
||||
<mat-error *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<p class="section">{{ 'USER.CREATE.PASSWORDSECTION' | translate }}</p>
|
||||
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PASSWORD.NEW' | translate }}</mat-label>
|
||||
<input autocomplete="off" name="firstpassword" matInput formControlName="password"
|
||||
type="password" />
|
||||
<mat-error *ngIf="password?.errors?.required">{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="password?.errors?.pattern">{{ 'USER.VALIDATION.INVALIDPATTERN' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="formfield">
|
||||
<mat-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</mat-label>
|
||||
<input autocomplete="off" name="firstpasswordRepeat" matInput formControlName="confirmPassword"
|
||||
type="password" />
|
||||
<mat-error *ngIf="confirmPassword?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
<div class="btn-container">
|
||||
<button color="accent" class="continue-button" [disabled]="orgForm.invalid || userForm.invalid"
|
||||
type="submit" mat-raised-button>{{ 'ACTIONS.FINISH' | translate }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentCreateStep == 1">
|
||||
<button (click)="next()" color="primary" mat-raised-button class="continue-button" cdkFocusInitial
|
||||
type="submit">
|
||||
{{'CONTINUE' | translate}}
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -0,0 +1,142 @@
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 4rem 4rem 2rem 4rem;
|
||||
|
||||
.abort-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.abort {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.abort-2 {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
mat-form-field {
|
||||
flex: 1 1 33%;
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: #81868a;
|
||||
font-size: .9rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-right {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.user {
|
||||
flex: 1;
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ffffff20;
|
||||
flex-wrap: wrap;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
width: 100%;
|
||||
display: block;
|
||||
font-size: .9rem;
|
||||
color: #81868a;
|
||||
}
|
||||
}
|
||||
.form {
|
||||
padding-top: 1rem;
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
button {
|
||||
margin-top: 3rem;
|
||||
display: block;
|
||||
padding: 0.5rem 4rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
.section {
|
||||
padding: .5rem;
|
||||
flex-basis: 100%;
|
||||
color: #81868a;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
&.nowrap{
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
flex: 1 0 33%;
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.formfield {
|
||||
width: 400px;
|
||||
input {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
&.autocomplete {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
button {
|
||||
border-radius: .5rem;
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.continue-button {
|
||||
margin-top: 3rem;
|
||||
display: block;
|
||||
padding: 0.5rem 4rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OrgCreateComponent } from './org-create.component';
|
||||
|
||||
describe('OrgCreateComponent', () => {
|
||||
let component: OrgCreateComponent;
|
||||
let fixture: ComponentFixture<OrgCreateComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [OrgCreateComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OrgCreateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { Location } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { CreateOrgRequest, Gender, OrgSetUpResponse, RegisterUserRequest } from 'src/app/proto/generated/admin_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
function passwordConfirmValidator(c: AbstractControl): any {
|
||||
if (!c.parent || !c) {
|
||||
return;
|
||||
}
|
||||
const pwd = c.parent.get('password');
|
||||
const cpwd = c.parent.get('confirmPassword');
|
||||
|
||||
if (!pwd || !cpwd) {
|
||||
return;
|
||||
}
|
||||
if (pwd.value !== cpwd.value) {
|
||||
return { invalid: true };
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-org-create',
|
||||
templateUrl: './org-create.component.html',
|
||||
styleUrls: ['./org-create.component.scss'],
|
||||
animations: [
|
||||
trigger('openClose', [
|
||||
transition(':enter', [
|
||||
style({ height: '0', opacity: 0 }),
|
||||
animate('150ms ease-in-out', style({ height: '*', opacity: 1 })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
animate('150ms ease-in-out', style({ height: '0', opacity: 0 })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class OrgCreateComponent {
|
||||
public orgForm!: FormGroup;
|
||||
public userForm!: FormGroup;
|
||||
|
||||
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
|
||||
public languages: string[] = ['de', 'en'];
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
private adminService: AdminService,
|
||||
private _location: Location,
|
||||
private fb: FormBuilder,
|
||||
) {
|
||||
this.orgForm = this.fb.group({
|
||||
name: ['', [Validators.required]],
|
||||
domain: ['', [Validators.required]],
|
||||
});
|
||||
|
||||
this.userForm = this.fb.group({
|
||||
firstName: ['', [Validators.required]],
|
||||
lastName: ['', [Validators.required]],
|
||||
displayName: [''],
|
||||
email: ['', [Validators.required]],
|
||||
gender: [''],
|
||||
nickName: [''],
|
||||
preferredLanguage: [''],
|
||||
password: ['', [Validators.required]],
|
||||
confirmPassword: ['', [Validators.required, passwordConfirmValidator]],
|
||||
});
|
||||
}
|
||||
|
||||
public createSteps: number = 2;
|
||||
public currentCreateStep: number = 1;
|
||||
|
||||
public finish(): void {
|
||||
const createOrgRequest: CreateOrgRequest = new CreateOrgRequest();
|
||||
createOrgRequest.setName(this.name?.value);
|
||||
createOrgRequest.setDomain(this.domain?.value);
|
||||
|
||||
const registerUserRequest: RegisterUserRequest = new RegisterUserRequest();
|
||||
registerUserRequest.setEmail(this.email?.value);
|
||||
registerUserRequest.setFirstName(this.firstName?.value);
|
||||
registerUserRequest.setLastName(this.lastName?.value);
|
||||
registerUserRequest.setNickName(this.nickName?.value);
|
||||
registerUserRequest.setDisplayName(this.displayName?.value);
|
||||
registerUserRequest.setGender(this.gender?.value);
|
||||
registerUserRequest.setPassword(this.password?.value);
|
||||
registerUserRequest.setPreferredLanguage(this.preferredLanguage?.value);
|
||||
|
||||
this.adminService
|
||||
.SetUpOrg(createOrgRequest, registerUserRequest)
|
||||
.then((data: OrgSetUpResponse) => {
|
||||
this.router.navigate(['orgs', data.toObject().org?.id]);
|
||||
})
|
||||
.catch(data => {
|
||||
this.toast.showError(data.message);
|
||||
});
|
||||
}
|
||||
|
||||
public next(): void {
|
||||
this.currentCreateStep++;
|
||||
}
|
||||
|
||||
public previous(): void {
|
||||
this.currentCreateStep--;
|
||||
}
|
||||
|
||||
public get name(): AbstractControl | null {
|
||||
return this.orgForm.get('name');
|
||||
}
|
||||
|
||||
public get domain(): AbstractControl | null {
|
||||
return this.orgForm.get('domain');
|
||||
}
|
||||
|
||||
|
||||
public get firstName(): AbstractControl | null {
|
||||
return this.userForm.get('firstName');
|
||||
}
|
||||
|
||||
public get lastName(): AbstractControl | null {
|
||||
return this.userForm.get('lastName');
|
||||
}
|
||||
|
||||
public get displayName(): AbstractControl | null {
|
||||
return this.userForm.get('displayName');
|
||||
}
|
||||
|
||||
public get email(): AbstractControl | null {
|
||||
return this.userForm.get('email');
|
||||
}
|
||||
|
||||
public get nickName(): AbstractControl | null {
|
||||
return this.userForm.get('nickName');
|
||||
}
|
||||
|
||||
public get preferredLanguage(): AbstractControl | null {
|
||||
return this.userForm.get('preferredLanguage');
|
||||
}
|
||||
|
||||
public get gender(): AbstractControl | null {
|
||||
return this.userForm.get('gender');
|
||||
}
|
||||
|
||||
public get password(): AbstractControl | null {
|
||||
return this.userForm.get('password');
|
||||
}
|
||||
|
||||
public get confirmPassword(): AbstractControl | null {
|
||||
return this.userForm.get('confirmPassword');
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._location.back();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { HttpLoaderFactory } from 'src/app/app.module';
|
||||
|
||||
import { OrgCreateRoutingModule } from './org-create-routing.module';
|
||||
import { OrgCreateComponent } from './org-create.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [OrgCreateComponent],
|
||||
imports: [
|
||||
OrgCreateRoutingModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatInputModule,
|
||||
MatFormFieldModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatSelectModule,
|
||||
TranslateModule.forChild({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient],
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class OrgCreateModule { }
|
||||
Reference in New Issue
Block a user