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:
Florian Forster
2020-05-13 14:41:43 +02:00
committed by GitHub
co-authored by Livio Amstutz Max Peintner
parent 9e32740eb8
commit 92a294f5c8
375 changed files with 97826 additions and 52 deletions
@@ -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 { }