Integra login/cadastro com API, toast de sucesso e redirecionamentos
This commit is contained in:
parent
71bc1b6b6e
commit
b66eb96879
|
|
@ -16,6 +16,7 @@
|
||||||
"@angular/platform-server": "^20.3.0",
|
"@angular/platform-server": "^20.3.0",
|
||||||
"@angular/router": "^20.3.0",
|
"@angular/router": "^20.3.0",
|
||||||
"@angular/ssr": "^20.3.10",
|
"@angular/ssr": "^20.3.10",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
|
@ -27,6 +28,7 @@
|
||||||
"@angular/build": "^20.3.10",
|
"@angular/build": "^20.3.10",
|
||||||
"@angular/cli": "^20.3.10",
|
"@angular/cli": "^20.3.10",
|
||||||
"@angular/compiler-cli": "^20.3.0",
|
"@angular/compiler-cli": "^20.3.0",
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"@types/node": "^20.17.19",
|
"@types/node": "^20.17.19",
|
||||||
|
|
@ -3587,6 +3589,16 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/bootstrap": {
|
||||||
|
"version": "5.2.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
|
||||||
|
"integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/connect": {
|
"node_modules/@types/connect": {
|
||||||
"version": "3.4.38",
|
"version": "3.4.38",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
"@angular/platform-server": "^20.3.0",
|
"@angular/platform-server": "^20.3.0",
|
||||||
"@angular/router": "^20.3.0",
|
"@angular/router": "^20.3.0",
|
||||||
"@angular/ssr": "^20.3.10",
|
"@angular/ssr": "^20.3.10",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
|
@ -42,6 +43,7 @@
|
||||||
"@angular/build": "^20.3.10",
|
"@angular/build": "^20.3.10",
|
||||||
"@angular/cli": "^20.3.10",
|
"@angular/cli": "^20.3.10",
|
||||||
"@angular/compiler-cli": "^20.3.0",
|
"@angular/compiler-cli": "^20.3.0",
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"@types/node": "^20.17.19",
|
"@types/node": "^20.17.19",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,26 @@
|
||||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
|
import {
|
||||||
|
ApplicationConfig,
|
||||||
|
provideBrowserGlobalErrorListeners,
|
||||||
|
provideZoneChangeDetection
|
||||||
|
} from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||||
|
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
import { authInterceptor } from './interceptors/auth.interceptor';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideRouter(routes), provideClientHydration(withEventReplay())
|
provideRouter(routes),
|
||||||
|
provideClientHydration(withEventReplay()),
|
||||||
|
|
||||||
|
// ✅ HttpClient com fetch + interceptor
|
||||||
|
provideHttpClient(
|
||||||
|
withFetch(),
|
||||||
|
withInterceptors([authInterceptor])
|
||||||
|
),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ import { Routes } from '@angular/router';
|
||||||
import { Home } from './pages/home/home';
|
import { Home } from './pages/home/home';
|
||||||
import { Register } from './pages/register/register';
|
import { Register } from './pages/register/register';
|
||||||
import { LoginComponent } from './pages/login/login';
|
import { LoginComponent } from './pages/login/login';
|
||||||
|
import { Geral } from './pages/geral/geral';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', component: Home },
|
{ path: '', component: Home },
|
||||||
{ path: "register", component: Register },
|
{ path: "register", component: Register },
|
||||||
{ path: "login", component: LoginComponent },
|
{ path: "login", component: LoginComponent },
|
||||||
|
{ path: "geral", component: Geral },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
background: linear-gradient(90deg, #030FAA 0%, #6066FF 45%, #C91EB5 100%);
|
background: linear-gradient(90deg, #030FAA 0%, #6066FF 45%, #C91EB5 100%);
|
||||||
padding: 10px 32px; /* bem mais baixo que antes */
|
padding: 10px 32px; /* bem mais baixo que antes */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
margin-top: -0.5px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { HttpInterceptorFn } from '@angular/common/http';
|
||||||
|
|
||||||
|
export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
if (!token) return next(req);
|
||||||
|
|
||||||
|
const authReq = req.clone({
|
||||||
|
setHeaders: { Authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
return next(authReq);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<p>geral works!</p>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Geral } from './geral';
|
||||||
|
|
||||||
|
describe('Geral', () => {
|
||||||
|
let component: Geral;
|
||||||
|
let fixture: ComponentFixture<Geral>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Geral]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Geral);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-geral',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './geral.html',
|
||||||
|
styleUrl: './geral.scss',
|
||||||
|
})
|
||||||
|
export class Geral {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Erro da API -->
|
||||||
|
<div *ngIf="apiError" class="alert alert-danger py-2 mb-3">
|
||||||
|
{{ apiError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Botão Entrar -->
|
<!-- Botão Entrar -->
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
@ -47,3 +52,22 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast (Sucesso Login) -->
|
||||||
|
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 2000;">
|
||||||
|
<div
|
||||||
|
#successToast
|
||||||
|
class="toast text-bg-success"
|
||||||
|
role="alert"
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-atomic="true"
|
||||||
|
>
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="me-auto">LineGestão</strong>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Fechar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
{{ toastMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
/* Wrapper para centralizar o card entre header e footer */
|
/* Wrapper para centralizar o card entre header e footer */
|
||||||
.login-wrapper {
|
.login-wrapper {
|
||||||
min-height: calc(100vh - 60px);
|
min-height: calc(100vh - 69.2px);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center; /* login fica no centro */
|
justify-content: center; /* login fica no centro */
|
||||||
|
|
@ -15,36 +15,64 @@
|
||||||
padding-bottom: 100px; /* mesmo “respiro” do cadastro */
|
padding-bottom: 100px; /* mesmo “respiro” do cadastro */
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
|
|
||||||
background: url('../../../assets/wallpaper/registro_qualidade3.png')
|
|
||||||
no-repeat right center;
|
|
||||||
background-size: contain;
|
|
||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
/* IMAGEM DESKTOP (PADRÃO) */
|
||||||
|
background-image: url('../../../assets/wallpaper/registro_login.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right top;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
/* 🔑 fundo preso ao viewport (igual cadastro) */
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOTEBOOKS / TABLETS */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.login-wrapper {
|
||||||
|
padding-top: 24px;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
padding-bottom: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
min-height: calc(100vh - 40px);
|
|
||||||
padding-top: 24px;
|
|
||||||
padding-bottom: 60px;
|
padding-bottom: 60px;
|
||||||
|
|
||||||
|
background-position: center top;
|
||||||
|
background-size: cover;
|
||||||
|
background-attachment: scroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CELULARES – IMAGEM MOBILE */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.login-wrapper {
|
||||||
|
min-height: calc(100vh - 40px);
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
|
||||||
|
background-image: url('../../../assets/wallpaper/mobile.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center top;
|
||||||
|
background-size: cover;
|
||||||
|
background-attachment: scroll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Card principal – mesmo estilo “glass” do cadastro,
|
/* ========================= */
|
||||||
mas SEM margin-left e centralizado pelo flex */
|
/* CARD DE LOGIN */
|
||||||
|
/* ========================= */
|
||||||
|
|
||||||
|
/* Desktop grande (monitor) */
|
||||||
.login-card {
|
.login-card {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 2px solid #c91eb5;
|
border: 2px solid #c91eb5;
|
||||||
|
|
||||||
max-width: 500px; /* aumenta a largura como no cadastro */
|
max-width: 480px; /* antes 500px */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 380px; /* um pouco menor que o cadastro, mas folgado */
|
min-height: 360px; /* antes 380px */
|
||||||
padding: 28px 24px; /* mesmo “respiro” interno */
|
padding: 26px 22px; /* antes 28px 24px */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
backdrop-filter: blur(6px);
|
backdrop-filter: blur(6px);
|
||||||
|
|
@ -52,10 +80,79 @@
|
||||||
|
|
||||||
.mb-3,
|
.mb-3,
|
||||||
.mb-4 {
|
.mb-4 {
|
||||||
margin-bottom: 0.9rem;
|
margin-bottom: 0.8rem; /* antes 0.9rem */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* NOTEB00KS (≤1440px) – deixa ainda mais compacto */
|
||||||
|
@media (max-width: 1440px) {
|
||||||
|
.login-card {
|
||||||
|
max-width: 430px;
|
||||||
|
min-height: 330px;
|
||||||
|
padding: 22px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title h2 {
|
||||||
|
font-size: 30px; /* levemente menor */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* notebooks / tablets (≤992px) */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.login-card {
|
||||||
|
max-width: 400px;
|
||||||
|
min-height: 310px;
|
||||||
|
padding: 20px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title h2 {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
height: 36px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn-submit {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 7px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3,
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* celulares (≤576px) – bem enxuto */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.login-card {
|
||||||
|
max-width: 340px;
|
||||||
|
min-height: auto;
|
||||||
|
padding: 18px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
height: 34px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn-submit {
|
||||||
|
font-size: 12.5px;
|
||||||
|
padding: 7px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ========================= */
|
||||||
|
/* TIPOGRAFIA E FORM */
|
||||||
|
/* ========================= */
|
||||||
|
|
||||||
/* Título centralizado rosa */
|
/* Título centralizado rosa */
|
||||||
.login-title {
|
.login-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -80,14 +177,14 @@
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Inputs iguais aos do cadastro (borda azul, maiores) */
|
/* Inputs iguais aos do cadastro (borda azul) */
|
||||||
.form-control {
|
.form-control {
|
||||||
height: 38px; /* antes 34px */
|
height: 38px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 2px solid #6066ff;
|
border: 2px solid #6066ff;
|
||||||
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px; /* antes 13px */
|
font-size: 14px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component, ElementRef, ViewChild, Inject, PLATFORM_ID } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
import {
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
FormBuilder,
|
import { Router } from '@angular/router';
|
||||||
FormGroup,
|
import { AuthService } from '../../services/auth.service';
|
||||||
Validators,
|
|
||||||
ReactiveFormsModule
|
|
||||||
} from '@angular/forms';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
|
|
@ -15,18 +12,56 @@ import {
|
||||||
styleUrls: ['./login.scss']
|
styleUrls: ['./login.scss']
|
||||||
})
|
})
|
||||||
export class LoginComponent {
|
export class LoginComponent {
|
||||||
|
|
||||||
loginForm: FormGroup;
|
loginForm: FormGroup;
|
||||||
isSubmitting = false;
|
isSubmitting = false;
|
||||||
|
apiError = '';
|
||||||
|
|
||||||
constructor(private fb: FormBuilder) {
|
toastMessage = '';
|
||||||
|
@ViewChild('successToast') successToast!: ElementRef;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: object
|
||||||
|
) {
|
||||||
this.loginForm = this.fb.group({
|
this.loginForm = this.fb.group({
|
||||||
username: ['', [Validators.required]], // “Usuário” do protótipo
|
username: ['', [Validators.required]], // aqui é email
|
||||||
password: ['', [Validators.required, Validators.minLength(6)]]
|
password: ['', [Validators.required, Validators.minLength(6)]]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async showToast(message: string) {
|
||||||
|
this.toastMessage = message;
|
||||||
|
|
||||||
|
// ✅ SSR-safe: só roda no browser
|
||||||
|
if (!isPlatformBrowser(this.platformId)) return;
|
||||||
|
|
||||||
|
const bs = await import('bootstrap');
|
||||||
|
const toast = new bs.Toast(this.successToast.nativeElement, { autohide: true, delay: 1500 });
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNameFromToken(token: string): string {
|
||||||
|
try {
|
||||||
|
const payload = token.split('.')[1];
|
||||||
|
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
const json = decodeURIComponent(
|
||||||
|
atob(base64)
|
||||||
|
.split('')
|
||||||
|
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
||||||
|
.join('')
|
||||||
|
);
|
||||||
|
const data = JSON.parse(json);
|
||||||
|
return data?.name ?? 'usuário';
|
||||||
|
} catch {
|
||||||
|
return 'usuário';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
|
this.apiError = '';
|
||||||
|
|
||||||
if (this.loginForm.invalid) {
|
if (this.loginForm.invalid) {
|
||||||
this.loginForm.markAllAsTouched();
|
this.loginForm.markAllAsTouched();
|
||||||
return;
|
return;
|
||||||
|
|
@ -34,30 +69,35 @@ export class LoginComponent {
|
||||||
|
|
||||||
this.isSubmitting = true;
|
this.isSubmitting = true;
|
||||||
|
|
||||||
// Simulação de envio – preparado para API futura
|
const v = this.loginForm.value;
|
||||||
console.log('Dados de login:', this.loginForm.value);
|
|
||||||
|
|
||||||
// FUTURO: aqui você injeta seu AuthService e chama a API .NET:
|
const payload = {
|
||||||
/*
|
email: v.username,
|
||||||
this.authService.login(this.loginForm.value).subscribe({
|
password: v.password
|
||||||
next: () => { ... },
|
};
|
||||||
error: () => { ... },
|
|
||||||
complete: () => this.isSubmitting = false
|
this.authService.login(payload).subscribe({
|
||||||
|
next: async (res) => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
|
||||||
|
const nome = this.getNameFromToken(res.token);
|
||||||
|
await this.showToast(`Bem-vindo, ${nome}!`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.router.navigate(['/geral']);
|
||||||
|
}, 900);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.apiError = err?.error ?? 'Erro ao fazer login.';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.isSubmitting = false;
|
|
||||||
alert('Login enviado (simulado). Depois conectamos na API .NET 😉');
|
|
||||||
}, 800);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasError(field: string, error?: string): boolean {
|
hasError(field: string, error?: string): boolean {
|
||||||
const control = this.loginForm.get(field);
|
const control = this.loginForm.get(field);
|
||||||
if (!control) return false;
|
if (!control) return false;
|
||||||
if (error) {
|
if (error) return control.touched && control.hasError(error);
|
||||||
return control.touched && control.hasError(error);
|
|
||||||
}
|
|
||||||
return control.touched && control.invalid;
|
return control.touched && control.invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,12 @@
|
||||||
|
|
||||||
<!-- Barra / título -->
|
<!-- Barra / título -->
|
||||||
<div class="register-title mb-4">
|
<div class="register-title mb-4">
|
||||||
<span class="register-icon">
|
<span class="register-icon">
|
||||||
<i class="bi bi-box-arrow-in-right"></i> <!-- ícone bootstrap -->
|
<i class="bi bi-box-arrow-in-right"></i>
|
||||||
</span>
|
</span>
|
||||||
<h2 class="mb-0">Cadastre-se</h2>
|
<h2 class="mb-0">Cadastre-se</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
|
||||||
|
|
||||||
<!-- Nome completo -->
|
<!-- Nome completo -->
|
||||||
|
|
@ -87,7 +86,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Botão Cadastrar (gradiente) -->
|
<!-- Erro da API -->
|
||||||
|
<div *ngIf="apiError" class="alert alert-danger py-2 mb-3">
|
||||||
|
{{ apiError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botão Cadastrar -->
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary w-100 register-btn mb-3"
|
class="btn btn-primary w-100 register-btn mb-3"
|
||||||
|
|
@ -106,3 +110,22 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast (Sucesso Cadastro) -->
|
||||||
|
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 2000;">
|
||||||
|
<div
|
||||||
|
#successToast
|
||||||
|
class="toast text-bg-success"
|
||||||
|
role="alert"
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-atomic="true"
|
||||||
|
>
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="me-auto">LineGestão</strong>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Fechar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
{{ toastMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,64 +4,164 @@
|
||||||
|
|
||||||
/* Wrapper para centralizar o card entre header e footer */
|
/* Wrapper para centralizar o card entre header e footer */
|
||||||
.register-wrapper {
|
.register-wrapper {
|
||||||
/* ocupa quase a tela toda, mas sem exagero */
|
/* mesma altura base do login */
|
||||||
min-height: calc(100vh - 60px);
|
min-height: calc(100vh - 69.2px);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: center; /* igual ao login */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
/* respiro mais equilibrado */
|
/* mesmos paddings do login */
|
||||||
padding-top: 32px;
|
padding-top: 32px;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
padding-bottom: 100px; /* ainda empurra o footer, mas menos que 140 */
|
padding-bottom: 100px;
|
||||||
padding-left: 5vw;
|
padding-left: 12px;
|
||||||
|
|
||||||
background: url('../../../assets/wallpaper/registro_qualidade2.png')
|
|
||||||
no-repeat right center;
|
|
||||||
background-size: contain;
|
|
||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
|
|
||||||
/* responsivo: em telas menores, centraliza o card e reduz paddings */
|
/* mesma imagem/config do login */
|
||||||
@media (max-width: 992px) {
|
background-image: url('../../../assets/wallpaper/registro_login.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right top;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
/* 🔑 fundo preso ao viewport → não “dá zoom” por causa da altura maior */
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOTEBOOKS / TABLETS */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.register-wrapper {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
padding-top: 24px;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
padding-bottom: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
min-height: calc(100vh - 40px);
|
|
||||||
padding-top: 24px;
|
|
||||||
padding-bottom: 60px;
|
padding-bottom: 60px;
|
||||||
|
|
||||||
|
background-position: center top;
|
||||||
|
background-size: cover;
|
||||||
|
background-attachment: scroll; /* evita bug em telas menores */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CELULARES – IMAGEM MOBILE */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.register-wrapper {
|
||||||
|
min-height: calc(100vh - 40px);
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
|
||||||
|
background-image: url('../../../assets/wallpaper/mobile.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center top;
|
||||||
|
background-size: cover;
|
||||||
|
background-attachment: scroll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ========================= */
|
||||||
|
/* CARD DE CADASTRO (MENOR) */
|
||||||
|
/* ========================= */
|
||||||
|
|
||||||
/* Card principal – AGORA MAIS LARGO */
|
/* Desktop grande (monitor) */
|
||||||
.register-card {
|
.register-card {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 2px solid #c91eb5;
|
border: 2px solid #c91eb5;
|
||||||
|
|
||||||
max-width: 500px; /* antes 340px ✅ aumenta a largura do card */
|
/* ➜ menor que antes */
|
||||||
|
max-width: 460px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 500px; /* leve ajuste pra não ficar “apertado” */
|
min-height: 430px;
|
||||||
padding: 28px 24px; /* um pouco mais de “respiro” interno */
|
padding: 22px 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-left: 150px;
|
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
backdrop-filter: blur(6px);
|
backdrop-filter: blur(6px);
|
||||||
-webkit-backdrop-filter: blur(6px);
|
-webkit-backdrop-filter: blur(6px);
|
||||||
|
|
||||||
.mb-3,
|
.mb-3,
|
||||||
.mb-4 {
|
.mb-4 {
|
||||||
margin-bottom: 0.9rem; /* mantém espaçamento confortável */
|
margin-bottom: 0.8rem; /* menos espaço entre os blocos */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Título: ícone + texto lado a lado */
|
/* NOTEBOOKS (≤1440px) – ainda mais compacto */
|
||||||
|
@media (max-width: 1440px) {
|
||||||
|
.register-card {
|
||||||
|
max-width: 420px;
|
||||||
|
min-height: 400px;
|
||||||
|
padding: 20px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-title h2 {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* notebooks / tablets (≤992px) */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.register-card {
|
||||||
|
max-width: 380px;
|
||||||
|
min-height: 360px;
|
||||||
|
padding: 18px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-title h2 {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
height: 36px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-btn,
|
||||||
|
.login-btn {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 7px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3,
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* celulares (≤576px) – compacto, mas confortável */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.register-card {
|
||||||
|
max-width: 330px;
|
||||||
|
min-height: auto;
|
||||||
|
padding: 18px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-title h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
height: 34px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-btn,
|
||||||
|
.login-btn {
|
||||||
|
font-size: 12.5px;
|
||||||
|
padding: 7px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ========================= */
|
||||||
|
/* TÍTULO + ÍCONE */
|
||||||
|
/* ========================= */
|
||||||
|
|
||||||
.register-title {
|
.register-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -78,7 +178,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ícone ao lado do título */
|
|
||||||
.register-icon {
|
.register-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -86,26 +185,29 @@
|
||||||
color: #c91eb5;
|
color: #c91eb5;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 32px;
|
font-size: 35px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Labels */
|
|
||||||
|
/* ========================= */
|
||||||
|
/* FORM / INPUTS / BOTÕES */
|
||||||
|
/* ========================= */
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px; /* um pouco maior pra acompanhar o card */
|
font-size: 14px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Inputs com borda azul do protótipo – levemente maiores */
|
|
||||||
.form-control {
|
.form-control {
|
||||||
height: 38px; /* antes 34px */
|
height: 38px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 2px solid #6066ff;
|
border: 2px solid #6066ff;
|
||||||
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px; /* antes 13px */
|
font-size: 14px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
|
@ -113,13 +215,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Botão principal (CADASTRAR) – rosa sólido */
|
|
||||||
.register-btn {
|
.register-btn {
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
border: none;
|
border: none;
|
||||||
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px; /* um tic maior */
|
font-size: 14px;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
padding: 9px 0;
|
padding: 9px 0;
|
||||||
|
|
@ -131,7 +232,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Botão "JÁ TEM UMA CONTA? ENTRE" – azul sólido */
|
|
||||||
.login-btn {
|
.login-btn {
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -149,7 +249,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mensagens de erro */
|
|
||||||
.text-danger.small {
|
.text-danger.small {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,30 @@
|
||||||
// src/app/pages/register.component.ts
|
import { Component, ElementRef, ViewChild, Inject, PLATFORM_ID } from '@angular/core';
|
||||||
import { Component } from '@angular/core';
|
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { RouterLink } from '@angular/router';
|
import { Router, RouterLink } from '@angular/router';
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-register',
|
selector: 'app-register',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ReactiveFormsModule, RouterLink],
|
imports: [CommonModule, ReactiveFormsModule, RouterLink],
|
||||||
templateUrl: './register.html',
|
templateUrl: './register.html',
|
||||||
styleUrls: ['./register.scss'] // ou .scss
|
styleUrls: ['./register.scss']
|
||||||
})
|
})
|
||||||
export class Register {
|
export class Register {
|
||||||
|
|
||||||
registerForm: FormGroup;
|
registerForm: FormGroup;
|
||||||
isSubmitting = false;
|
isSubmitting = false;
|
||||||
|
apiError = '';
|
||||||
|
|
||||||
constructor(private fb: FormBuilder) {
|
toastMessage = '';
|
||||||
|
@ViewChild('successToast') successToast!: ElementRef;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: object
|
||||||
|
) {
|
||||||
this.registerForm = this.fb.group({
|
this.registerForm = this.fb.group({
|
||||||
fullName: ['', [Validators.required, Validators.minLength(3)]],
|
fullName: ['', [Validators.required, Validators.minLength(3)]],
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
|
@ -28,14 +36,26 @@ export class Register {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validador para confirmar senha
|
|
||||||
private passwordsMatchValidator(group: FormGroup) {
|
private passwordsMatchValidator(group: FormGroup) {
|
||||||
const pass = group.get('password')?.value;
|
const pass = group.get('password')?.value;
|
||||||
const confirm = group.get('confirmPassword')?.value;
|
const confirm = group.get('confirmPassword')?.value;
|
||||||
return pass === confirm ? null : { passwordsMismatch: true };
|
return pass === confirm ? null : { passwordsMismatch: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async showToast(message: string) {
|
||||||
|
this.toastMessage = message;
|
||||||
|
|
||||||
|
// ✅ SSR-safe: só roda no browser
|
||||||
|
if (!isPlatformBrowser(this.platformId)) return;
|
||||||
|
|
||||||
|
const bs = await import('bootstrap');
|
||||||
|
const toast = new bs.Toast(this.successToast.nativeElement, { autohide: true, delay: 1800 });
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
|
this.apiError = '';
|
||||||
|
|
||||||
if (this.registerForm.invalid) {
|
if (this.registerForm.invalid) {
|
||||||
this.registerForm.markAllAsTouched();
|
this.registerForm.markAllAsTouched();
|
||||||
return;
|
return;
|
||||||
|
|
@ -43,33 +63,40 @@ export class Register {
|
||||||
|
|
||||||
this.isSubmitting = true;
|
this.isSubmitting = true;
|
||||||
|
|
||||||
// Por enquanto: apenas exibe os dados e "simula" o envio
|
const v = this.registerForm.value;
|
||||||
console.log('Dados de cadastro:', this.registerForm.value);
|
|
||||||
|
|
||||||
// TODO FUTURO: quando a API .NET estiver pronta,
|
const payload = {
|
||||||
// você injeta um AuthService e envia para o backend:
|
name: v.fullName,
|
||||||
/*
|
email: v.email,
|
||||||
this.authService.register(this.registerForm.value).subscribe({
|
phone: v.phone,
|
||||||
next: () => { ... },
|
password: v.password,
|
||||||
error: () => { ... },
|
confirmPassword: v.confirmPassword
|
||||||
complete: () => this.isSubmitting = false
|
};
|
||||||
|
|
||||||
|
this.authService.register(payload).subscribe({
|
||||||
|
next: async () => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
|
||||||
|
// Se você não quer manter "logado" após cadastrar:
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
|
||||||
|
await this.showToast('Cadastro realizado com sucesso! Agora faça login para continuar.');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.apiError = err?.error ?? 'Erro ao cadastrar.';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
// Simulação de atraso só para UX (remova se quiser)
|
|
||||||
setTimeout(() => {
|
|
||||||
this.isSubmitting = false;
|
|
||||||
this.registerForm.reset();
|
|
||||||
alert('Cadastro realizado (simulado). Quando a API estiver pronta, isso irá de fato registrar o usuário.');
|
|
||||||
}, 800);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasError(field: string, error?: string): boolean {
|
hasError(field: string, error?: string): boolean {
|
||||||
const control = this.registerForm.get(field);
|
const control = this.registerForm.get(field);
|
||||||
if (!control) return false;
|
if (!control) return false;
|
||||||
if (error) {
|
if (error) return control.touched && control.hasError(error);
|
||||||
return control.touched && control.hasError(error);
|
|
||||||
}
|
|
||||||
return control.touched && control.invalid;
|
return control.touched && control.invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
export interface RegisterPayload {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginPayload {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AuthService {
|
||||||
|
private baseUrl = `${environment.apiUrl}/auth`;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
register(payload: RegisterPayload) {
|
||||||
|
return this.http.post<{ token: string }>(`${this.baseUrl}/register`, payload)
|
||||||
|
.pipe(tap(r => localStorage.setItem('token', r.token)));
|
||||||
|
}
|
||||||
|
|
||||||
|
login(payload: LoginPayload) {
|
||||||
|
return this.http.post<{ token: string }>(`${this.baseUrl}/login`, payload)
|
||||||
|
.pipe(tap(r => localStorage.setItem('token', r.token)));
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
get token(): string | null {
|
||||||
|
return localStorage.getItem('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggedIn(): boolean {
|
||||||
|
return !!this.token;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.1 MiB |
|
Before Width: | Height: | Size: 4.7 MiB After Width: | Height: | Size: 4.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.9 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 MiB |
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const environment = {
|
||||||
|
production: false,
|
||||||
|
apiUrl: 'https://localhost:7205'
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue