implement part 3
This commit is contained in:
parent
c7707fc94a
commit
f2f7e4e863
|
@ -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)');
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)];
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
;
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -91,4 +91,6 @@ $sidebar-width: 250px;
|
|||
}
|
||||
.content{
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
.container{
|
||||
max-width: 720px;
|
||||
}
|
||||
.form-footer{
|
||||
width: 100%;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -51,4 +51,9 @@ export const profileRoutes: Route[] = [
|
|||
component: ProfileEditComponent,
|
||||
children: profileTabRoutes,
|
||||
},
|
||||
{
|
||||
path: ':personId',
|
||||
component: ProfileEditComponent,
|
||||
children: profileTabRoutes,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"AVATAR": "Avatar",
|
||||
"PASSWORD": "Password",
|
||||
"SITE_SETTINGS": "Site settings",
|
||||
"SAVE": "Save",
|
||||
"CONTACTS": {
|
||||
"COUNTRY": "Country",
|
||||
"STATE": "State",
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"AVATAR": "Zdjęcie profilowe",
|
||||
"PASSWORD": "Hasło",
|
||||
"SITE_SETTINGS": "Ustawienia strony",
|
||||
"SAVE": "Zapisz",
|
||||
"CONTACTS": {
|
||||
"COUNTRY": "Kraj",
|
||||
"STATE": "Województwo",
|
||||
|
|
Loading…
Reference in New Issue