implement part 3

This commit is contained in:
Sieciech 2021-09-01 17:52:14 +02:00
parent c7707fc94a
commit f2f7e4e863
22 changed files with 516 additions and 196 deletions

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210901131245 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SEQUENCE person_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE person (id INT NOT NULL, country_id INT DEFAULT NULL, state_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, surname VARCHAR(255) NOT NULL, city VARCHAR(255) DEFAULT NULL, zip VARCHAR(7) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_34DCD176F92F3E70 ON person (country_id)');
$this->addSql('CREATE INDEX IDX_34DCD1765D83CC1 ON person (state_id)');
$this->addSql('ALTER TABLE person ADD CONSTRAINT FK_34DCD176F92F3E70 FOREIGN KEY (country_id) REFERENCES country (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE person ADD CONSTRAINT FK_34DCD1765D83CC1 FOREIGN KEY (state_id) REFERENCES country_region (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE "user" DROP CONSTRAINT fk_8d93d649f92f3e70');
$this->addSql('ALTER TABLE "user" DROP CONSTRAINT fk_8d93d6495d83cc1');
$this->addSql('DROP INDEX idx_8d93d649f92f3e70');
$this->addSql('DROP INDEX idx_8d93d6495d83cc1');
$this->addSql('ALTER TABLE "user" ADD person_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE "user" DROP country_id');
$this->addSql('ALTER TABLE "user" DROP state_id');
$this->addSql('ALTER TABLE "user" DROP name');
$this->addSql('ALTER TABLE "user" DROP surname');
$this->addSql('ALTER TABLE "user" DROP avatar');
$this->addSql('ALTER TABLE "user" ADD CONSTRAINT FK_8D93D649217BBB47 FOREIGN KEY (person_id) REFERENCES person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649217BBB47 ON "user" (person_id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE "user" DROP CONSTRAINT FK_8D93D649217BBB47');
$this->addSql('DROP SEQUENCE person_id_seq CASCADE');
$this->addSql('DROP TABLE person');
$this->addSql('DROP INDEX UNIQ_8D93D649217BBB47');
$this->addSql('ALTER TABLE "user" ADD state_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE "user" ADD name VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE "user" ADD surname VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE "user" ADD avatar VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE "user" RENAME COLUMN person_id TO country_id');
$this->addSql('ALTER TABLE "user" ADD CONSTRAINT fk_8d93d649f92f3e70 FOREIGN KEY (country_id) REFERENCES country (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE "user" ADD CONSTRAINT fk_8d93d6495d83cc1 FOREIGN KEY (state_id) REFERENCES country_region (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX idx_8d93d649f92f3e70 ON "user" (country_id)');
$this->addSql('CREATE INDEX idx_8d93d6495d83cc1 ON "user" (state_id)');
}
}

View File

@ -3,6 +3,7 @@
namespace App\Controller;
use App\Entity\User;
use App\Entity\Person;
use App\Traits\JsonResponseTrait;
use App\Repository\UserRepository;
use Symfony\Component\HttpFoundation\Request;
@ -37,13 +38,20 @@ class AuthController extends AbstractController
return $this->notAcceptable("User email exists");
}
$person = new Person();
$person->setName($name);
$person->setSurname($surname);
$person->generateAvatar($email);
$em->persist($person);
$em->flush();
$user = new User();
$user->setPassword($encoder->hashPassword($user, $password));
$user->setEmail($email);
$user->setName($name);
$user->setSurname($surname);
$user->setPerson($person);
$em->persist($user);
$em->flush();
return $this->created($user);
}
@ -60,7 +68,7 @@ class AuthController extends AbstractController
if (!$user) {
return $this->unauthorized([]);
}
$user->generateAvatar();
$user->getPerson()->generateAvatar($user->getEmail());
return $this->ok($user);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Controller;
use App\Traits\JsonResponseTrait;
use App\Repository\PersonRepository;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class PersonController extends AbstractController
{
use JsonResponseTrait;
public function __construct(private PersonRepository $personRepository) {
}
#[Route('/api/person/{personId}', methods: ["GET"])]
public function show(int $personId)
{
$person = $this->personRepository->get($personId);
return $this->ok($person);
}
}

View File

@ -15,7 +15,7 @@ class BaseEntity {
$output = [];
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
return $object->getName();
return '$' . basename(str_replace('\\', '/', $object::class));
},
];
$normalizers = [new ObjectNormalizer(defaultContext: $defaultContext)];

View File

@ -0,0 +1,150 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\PersonRepository;
use App\Entity\Abstraction\BaseEntity;
/**
* @ORM\Entity(repositoryClass=PersonRepository::class)
*/
class Person extends BaseEntity
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* @ORM\Column(type="string", length=255)
*/
private $surname;
/**
* @ORM\ManyToOne(targetEntity=Country::class, nullable=true)
*/
private $country;
/**
* @ORM\ManyToOne(targetEntity=CountryRegion::class, nullable=true)
*/
private $state;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $city;
/**
* @ORM\Column(type="string", length=7, nullable=true)
*/
private $zip;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $avatar;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getSurname(): ?string
{
return $this->surname;
}
public function setSurname(string $surname): self
{
$this->surname = $surname;
return $this;
}
public function getCountry(): ?Country
{
return $this->country;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
public function getState(): ?CountryRegion
{
return $this->state;
}
public function setState(?CountryRegion $state): self
{
$this->state = $state;
return $this;
}
public function getCity(): ?string
{
return $this->city;
}
public function setCity(string $city): self
{
$this->city = $city;
return $this;
}
public function getZip(): ?string
{
return $this->zip;
}
public function setZip(string $zip): self
{
$this->zip = $zip;
return $this;
}
public function getAvatar(): ?string
{
return $this->avatar;
}
public function setAvatar(string $avatar): self
{
$this->avatar = $avatar;
return $this;
}
public function generateAvatar($email) {
if (!$this->avatar) {
$this->avatar = 'https://www.gravatar.com/avatar/' . md5($email) . '?s=512&d=robohash';
}
}
}

View File

@ -2,10 +2,11 @@
namespace App\Entity;
use App\Entity\Person;
use App\Enums\UserRoleEnum;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\UserRepository;
use App\Entity\Abstraction\BaseEntity;
use App\Enums\UserRoleEnum;
use Symfony\Component\Security\Core\User\UserInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
@ -42,29 +43,9 @@ class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUse
private $email;
/**
* @ORM\Column(type="string", length=255)
* @ORM\OneToOne(targetEntity=Person::class, cascade={"persist", "remove"})
*/
private $name;
/**
* @ORM\Column(type="string", length=255)
*/
private $surname;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $avatar;
/**
* @ORM\ManyToOne(targetEntity=Country::class)
*/
private $country;
/**
* @ORM\ManyToOne(targetEntity=CountryRegion::class)
*/
private $state;
private $person;
public static function createFromPayload($username, array $payload)
@ -174,68 +155,14 @@ class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUse
return $this;
}
public function getName(): ?string
public function getPerson(): ?Person
{
return $this->name;
return $this->person;
}
public function setName(string $name): self
public function setPerson(?Person $person): self
{
$this->name = $name;
return $this;
}
public function getSurname(): ?string
{
return $this->surname;
}
public function setSurname(string $surname): self
{
$this->surname = $surname;
return $this;
}
public function getAvatar(): ?string
{
return $this->avatar;
}
public function setAvatar(?string $avatar): self
{
$this->avatar = $avatar;
return $this;
}
public function generateAvatar() {
if (!$this->avatar) {
$this->avatar = 'https://www.gravatar.com/avatar/' . md5($this->email) . '?s=514&d=robohash';
}
}
public function getCountry(): ?Country
{
return $this->country;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
public function getState(): ?CountryRegion
{
return $this->state;
}
public function setState(?CountryRegion $state): self
{
$this->state = $state;
$this->person = $person;
return $this;
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\Person;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Person|null find($id, $lockMode = null, $lockVersion = null)
* @method Person|null findOneBy(array $criteria, array $orderBy = null)
* @method Person[] findAll()
* @method Person[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class PersonRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Person::class);
}
// /**
// * @return Person[] Returns an array of Person objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('p')
->andWhere('p.exampleField = :val')
->setParameter('val', $value)
->orderBy('p.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
public function get(int $personId): ?Person
{
return $this->createQueryBuilder('p')
->andWhere('p.id = :id')
->setParameter('id', $personId)
->getQuery()
->getOneOrNullResult()
;
}
}

View File

@ -41,8 +41,8 @@
</div>
<div>
<div class="d-flex user-box" *ngIf="user">
<div class="avatar" [style.background-image]="url(user.avatar)"></div>
<div class="d-flex align-items-center flex-grow-1 p-2">{{ user.name }} {{ user.surname }}</div>
<div class="avatar" [style.background-image]="url(user.person.avatar)"></div>
<div class="d-flex align-items-center flex-grow-1 p-2">{{ user.person.name }} {{ user.person.surname }}</div>
<div class="user-menu-container">
<div class="user-menu">
<button mat-icon-button [matMenuTriggerFor]="usermenu">

View File

@ -91,4 +91,6 @@ $sidebar-width: 250px;
}
.content{
flex-grow: 1;
position: relative;
overflow: auto;
}

View File

@ -0,0 +1,16 @@
import { UserModel } from "./user.model";
export class PersonModel {
id: number;
name: string;
surname: string;
avatar: string | null;
country: number | null;
state: string | null;
constructor(data: PersonModel) {
if (data) {
Object.assign(this, data);
}
}
}

View File

@ -1,11 +1,17 @@
import { UserRoleEnum } from '../enums/user-role.enum';
import { PersonModel } from './person.model';
export interface UserModel {
export class UserModel {
id: number;
name: string;
surname: string;
avatar: string | null;
country: number | null;
state: string | null;
roles: UserRoleEnum[];
person: PersonModel;
constructor(data?: UserModel) {
if (data) {
Object.assign(this, data);
if (data.person && typeof data.person !== 'string') {
this.person = new PersonModel(data.person);
}
}
}
}

View File

@ -1,7 +1,6 @@
<div class="full-height-form">
<div class="form-content position-relative">
<div class="scrollable-content" (scroll)="scroll($event)">
<div [style.height.px]="placeholderBoxHeight"></div>
<div>
<div class="container" [formGroup]="form">
<div class="form-group">
@ -103,7 +102,10 @@
</div>
</div>
</div>
<div class="form-footer">
<button mat-flat-button color="primary">zapisz</button>
</div>
</div>
<ng-template #footer>
<div class="form-footer">
<button mat-flat-button color="primary">{{ 'PROFILE.SAVE' | translate }}</button>
</div>
</ng-template>

View File

@ -1,3 +1,6 @@
.container{
max-width: 720px;
}
.form-footer{
width: 100%;
}

View File

@ -1,10 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@ng-stack/forms';
import { Observable, Subject } from 'rxjs';
import { CountryModel } from 'src/app/modules/shared/models/country.model';
import { CountryService } from 'src/app/modules/shared/services/country/country.service';
import { map, startWith } from 'rxjs/operators';
import { AuthService } from 'src/app/modules/auth/services/auth/auth.service';
import { ProfileEditComponent } from '../profile-edit/profile-edit.component';
interface PhoneEntry{
number: string;
@ -39,28 +40,27 @@ class ProfileInformationBasicModel {
export class ProfileEditBasicsComponent implements OnInit {
form: FormGroup<ProfileInformationBasicModel>;
onContentScroll = new Subject<number>();
headerHeight = 200;
placeholderBoxHeight = 0;
filteredCountries: Observable<CountryModel[]>;
countryInputChnage = new Subject<string>();
countries: CountryModel[] = [];
countryInput = '';
@ViewChild('footer') footerElement: ElementRef;
constructor(
private countryService: CountryService,
authService: AuthService,
formBuilder: FormBuilder,
parent: ProfileEditComponent,
) {
const phones = formBuilder.array<PhoneEntry>([]);
const emails = formBuilder.array<EmailEntry>([]);
const user = authService.getUser();
this.form = formBuilder.group<ProfileInformationBasicModel>({
name: user.name,
surname: user.surname,
name: parent.person.name,
surname: parent.person.surname,
country: user.country,
country: parent.person.country,
state: 'state',
city: 'city',
zip: 'zip',
@ -89,14 +89,6 @@ export class ProfileEditBasicsComponent implements OnInit {
ngOnInit(): void {
}
scroll(e): void {
const scrollTop = e.target?.scrollTop;
if (Number(scrollTop) === scrollTop) {
this.placeholderBoxHeight = Math.min(scrollTop, this.headerHeight);
this.onContentScroll.next(scrollTop);
}
}
filterCountries(name: string): CountryModel[] {
return this.countries.filter(i => i.name.toLowerCase().includes(name));
}

View File

@ -1,46 +1,49 @@
<div>
<div class="toolbar-background user-cover"></div>
<div class="scroller" [style.--scrollbar-width]="scrollWidth+'px'">
<div class="user-cover">
<div class="toolbar-background"></div>
</div>
<div class="user-header-container">
<div class="scrollable-header" [style.--scrolled]="scrolled">
<div #headerContent>
<div class="p-4" >
<mat-card *ngIf="user === null" class="loading-card">
loading
</mat-card>
<mat-card *ngIf="user !== null">
<div class="user-header">
<div>
<div class="user-avatar" [style.background-image]="url(user.avatar)"></div>
</div>
<div class="d-flex align-items-end flex-grow-1">
<h3>{{ user.name }} {{ user.surname }}</h3>
</div>
<div class="header" #scrollHeader [style.margin-top.px]="-headerMove">
<div class="p-4">
<mat-card *ngIf="person === undefined" class="loading-card">
loading
</mat-card>
<mat-card *ngIf="person !== undefined">
<div class="user-header">
<div>
<div class="user-avatar" [style.background-image]="url(person.avatar)"></div>
</div>
</mat-card>
<div class="d-flex align-items-end flex-grow-1">
<h3>{{ person.name }} {{ person.surname }}</h3>
</div>
</div>
</mat-card>
</div>
<div *ngIf="person !== null" class="main-background">
<div class="p-3 d-block d-md-none">
<h6>{{ currentTabName | translate }}</h6>
</div>
<div class="tab-container">
<mat-tab-group [selectedIndex]="selectedIndex" (selectedIndexChange)="selectedIndexChange($event)">
<ng-container *ngFor="let tab of tabs">
<mat-tab>
<ng-template mat-tab-label>
<i *ngIf="tab.icon" class="me-0 me-md-3" [ngClass]="tab.icon"></i>
<span class="d-none d-md-inline-block">{{ tab.label | translate }}</span>
</ng-template>
</mat-tab>
</ng-container>
</mat-tab-group>
</div>
</div>
</div>
</div>
<div *ngIf="user !== null" class="main-background">
<div class="p-3 d-block d-md-none">
<h6>{{ currentTabName | translate }}</h6>
</div>
<div class="tab-container">
<mat-tab-group [selectedIndex]="selectedIndex" (selectedIndexChange)="selectedIndexChange($event)">
<ng-container *ngFor="let tab of tabs">
<mat-tab>
<ng-template mat-tab-label>
<i *ngIf="tab.icon" class="me-0 me-md-3" [ngClass]="tab.icon"></i>
<span class="d-none d-md-inline-block">{{ tab.label | translate }}</span>
</ng-template>
</mat-tab>
</ng-container>
</mat-tab-group>
<div class="content" #scrollContent [style.padding-top.px]="headerHeight" (scroll)="onscrol($event)">
<div class="h-100 main-background" *ngIf="person !== undefined && loaded === true">
<router-outlet (activate)="activated($event)" (deactivate)="deactivated($event)"></router-outlet>
</div>
</div>
</div>
<div class="flex-grow-1 main-background" *ngIf="user !== null && loaded === true">
<div class="h-100">
<router-outlet (activate)="activated($event)" (deactivate)="deactivated($event)"></router-outlet>
<div class="footer" *ngIf="footerElement !== undefined">
<ng-container *ngTemplateOutlet="footerElement"></ng-container>
</div>
</div>

View File

@ -3,21 +3,19 @@
display: flex;
flex-direction: column;
}
.scrollable-header{
overflow: hidden;
> * {
margin-top: calc(var(--scrolled) * -1px);
margin-bottom: calc(var(--scrolled) * -1px);
}
}
.user-cover{
height: 100px;
background-color: var(--toolbar-background);
position: relative;
z-index: -1;
height: 0px;
width: calc(100% - var(--scrollbar-width));
div{
display: block;
content: "";
height: 100px;
background-color: var(--toolbar-background);
}
}
.user-header-container{
margin-top: -100px;
position: relative;
z-index: 20;
}
@ -42,4 +40,32 @@
}
.main-background{
background: var(--main-background);
}
.scroller{
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
--scrollbar-width: 0px;
.header{
position: relative;
top: 0px;
left: 0px;
width: calc(100% - var(--scrollbar-width));
z-index: 20;
}
.content{
position: relative;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
overflow: auto;
z-index: 10;
}
}

View File

@ -1,12 +1,11 @@
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { ActivatedRoute, ActivatedRouteSnapshot, ActivationStart, Data, Router, RoutesRecognized } from '@angular/router';
import { Subscription } from 'rxjs';
import { UserModel } from 'src/app/modules/auth/models/user.model';
import { PersonModel } from 'src/app/modules/auth/models/person.model';
import { AuthService } from 'src/app/modules/auth/services/auth/auth.service';
import { ProfileTabEnum } from '../../enums/profile-tab.enum';
import { profileTabRoutes } from '../../profile-routing';
import { PersonService } from '../../service/person/person.service';
interface ProfileTab {
tab: ProfileTabEnum;
@ -21,15 +20,19 @@ interface ProfileTab {
})
export class ProfileEditComponent implements AfterViewInit {
scrollSubscriptions: Subscription[] = [];
scrolled = 0;
profileTabRoutes = profileTabRoutes;
currentTabName: string;
user: UserModel | null = null;
person: PersonModel | null = null;
selectedIndex = 0;
defaultProfileTab = ProfileTabEnum.Basics;
footerElement: ElementRef;
loaded = false;
@ViewChild('headerContent', {static: false}) headerContent: ElementRef;
@ViewChild('scrollHeader', {static: false}) scrollHeader: ElementRef;
@ViewChild('scrollContent', {static: false}) scrollContent: ElementRef;
headerHeight = 200;
headerMove = 0;
maxHeaderMove = 0;
scrollWidth = 30;
tabs: ProfileTab[] = [
{
tab: ProfileTabEnum.Basics,
@ -63,11 +66,15 @@ export class ProfileEditComponent implements AfterViewInit {
authService: AuthService,
private activatedRoute: ActivatedRoute,
private router: Router,
private personService: PersonService,
) {
this.user = authService.getUser();
authService.userChange.subscribe(user => {
this.user = user;
});
const personId = parseInt(activatedRoute.snapshot.params.personId, 10);
if (personId) {
this.loadPerson(personId);
} else {
this.loadPerson(authService.getUser().person.id);
}
this.onRouteChange(activatedRoute.snapshot.firstChild?.data);
this.router.events.subscribe(event => {
if (event instanceof RoutesRecognized) {
@ -76,28 +83,50 @@ export class ProfileEditComponent implements AfterViewInit {
});
}
loadPerson(personId: number) {
this.person = undefined;
this.loaded = false;
this.personService.get(personId).subscribe(person => {
this.person = person;
this.loaded = true;
});
}
updateScroller() {
this.scrollHeader.nativeElement.style.height = '0px';
this.headerHeight = this.scrollHeader.nativeElement.scrollHeight;
this.maxHeaderMove = this.scrollHeader.nativeElement.firstChild.scrollHeight;
this.scrollWidth = this.scrollContent.nativeElement.parentElement.scrollWidth - this.scrollContent.nativeElement.scrollWidth;
}
ngAfterViewInit(): void {
this.loaded = true;
setTimeout(() => {
this.updateScroller();
}, 50);
}
activated(component): void {
if (component.onContentScroll) {
component.headerHeight = this.headerContent.nativeElement.scrollHeight;
setTimeout(() => {
component.headerHeight = this.headerContent.nativeElement.scrollHeight;
}, 500);
this.scrollSubscriptions.push(
component.onContentScroll.subscribe(position => {
this.scrolled = position / 2;
})
);
activated(component) {
if (component.footerElement) {
this.footerElement = component.footerElement;
}
setTimeout(() => {
if (component.footerElement) {
this.footerElement = component.footerElement;
}
this.updateScroller();
}, 50);
}
deactivated(component): void {
this.scrolled = 0;
this.scrollSubscriptions.forEach(i => i.unsubscribe());
this.scrollSubscriptions = [];
deactivated(component) {
this.footerElement = undefined;
setTimeout(() => {
this.updateScroller();
}, 50);
}
onscrol(e) {
this.headerMove = Math.min(e.target.scrollTop, this.maxHeaderMove);
}
onRouteChange(data: Data): void {

View File

@ -51,4 +51,9 @@ export const profileRoutes: Route[] = [
component: ProfileEditComponent,
children: profileTabRoutes,
},
{
path: ':personId',
component: ProfileEditComponent,
children: profileTabRoutes,
},
];

View File

@ -0,0 +1,17 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { PersonModel } from 'src/app/modules/auth/models/person.model';
@Injectable({
providedIn: 'root'
})
export class PersonService {
constructor(private http: HttpClient) {
}
get(personId: number): Observable<PersonModel> {
return this.http.get<PersonModel>(`/api/person/${personId}`);
}
}

View File

@ -1,7 +1,7 @@
.form-group{
display: flex;
flex-wrap: wrap;
margin: 1rem;
padding: 1rem;
&:not(:last-child){
margin-bottom: 2rem;
}
@ -37,17 +37,17 @@
flex-grow: 1;
padding: 0.5rem;
}
.form-footer{
background: var(--dialog-background);
border-top: 1px solid var(--divider-color);
padding: 0.5rem;
}
.form-footer{
background: var(--dialog-background);
border-top: 1px solid var(--divider-color);
padding: 0.5rem;
display: flex;
justify-content: flex-end;
> div{
display: flex;
justify-content: flex-end;
> div{
display: flex;
&:first-child{
flex-grow: 1;
}
&:first-child{
flex-grow: 1;
}
}
}

View File

@ -50,6 +50,7 @@
"AVATAR": "Avatar",
"PASSWORD": "Password",
"SITE_SETTINGS": "Site settings",
"SAVE": "Save",
"CONTACTS": {
"COUNTRY": "Country",
"STATE": "State",

View File

@ -50,6 +50,7 @@
"AVATAR": "Zdjęcie profilowe",
"PASSWORD": "Hasło",
"SITE_SETTINGS": "Ustawienia strony",
"SAVE": "Zapisz",
"CONTACTS": {
"COUNTRY": "Kraj",
"STATE": "Województwo",