Modernizacao de componentes

This commit is contained in:
Eduardo 2025-12-11 18:05:45 -03:00
parent e40e93280d
commit 71bc1b6b6e
21 changed files with 726 additions and 141 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,6 +1,10 @@
import { Routes } from '@angular/router';
import { Home } from './pages/home/home';
import { Register } from './pages/register/register';
import { LoginComponent } from './pages/login/login';
export const routes: Routes = [
{ path: '', component: Home },
{ path: "register", component: Register },
{ path: "login", component: LoginComponent },
];

View File

@ -1,10 +1,11 @@
<button
type="button"
(click)="onClick()"
[style.width.px]="width"
[style.height.px]="height"
[style.width]="width"
[style.height]="height"
[style.background]="background"
[style.color]="color"
[style.font-size.px]="fontSize"
[style.font-size]="fontSize"
[style.font-weight]="fontWeight"
class="cta-button"
>

View File

@ -13,7 +13,7 @@ export class CtaButtonComponent {
@Input() height: string = '55px';
@Input() background: string = '#C91EB5';
@Input() color: string = '#FFFFFF';
@Input() fontSize: string = '18px';
@Input() fontSize: string = '16.5px';
@Input() fontWeight: string = '700';
@Output() clicked = new EventEmitter<void>();

View File

@ -1,122 +1,79 @@
/* ===================================== */
/* FOOTER CONTAINER */
/* FOOTER CONTAINER VERSÃO MODERNA */
/* ===================================== */
.footer-container {
width: 100%;
background: rgba(3, 15, 170, 0.93);
padding: 10px 60px; /* ⬅️ TOPO/FUNDO DO FOOTER (antes 16px 60px) */
/* Degradê com as cores da marca */
background: linear-gradient(90deg, #030FAA 0%, #6066FF 45%, #C91EB5 100%);
padding: 10px 32px; /* bem mais baixo que antes */
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: flex-start;
align-items: center;
gap: 24px;
font-family: "Inter", sans-serif;
color: #FFFFFF;
/* 💻 NOTEBOOKS 12001399px */
@media (min-width: 1200px) and (max-width: 1399.98px) {
padding: 6px 50px; /* ⬅️ TOPO/FUNDO NO NOTEBOOK (antes 8px 50px) */
}
border-top: 1px solid rgba(255, 255, 255, 0.12);
/* Telas < 1200px — empilha esquerda e direita */
/* Suave sombra pra destacar do conteúdo */
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.12);
/* Telas médias e abaixo empilha conteúdo */
@media (max-width: 1199.98px) {
flex-direction: column;
justify-content: center;
align-items: center;
padding: 14px 30px; /* ⬅️ TOPO/FUNDO EM TELAS MÉDIAS (antes 18px 30px) */
text-align: center;
padding: 12px 20px;
}
/* Tablets e celulares */
@media (max-width: 768px) {
padding: 14px 20px; /* ⬅️ TOPO/FUNDO EM MOBILE (antes 18px 20px) */
align-items: flex-start;
text-align: left;
padding: 12px 16px;
}
}
/* ===================================== */
/* LADO ESQUERDO */
/* LADO ESQUERDO (TEXTOS) */
/* ===================================== */
.footer-left {
margin-top: 100px; /* ⬅️ DISTÂNCIA DO TEXTO PRO TOPO NO DESKTOP (antes 100px) */
/* 💻 NOTEBOOKS 12001399px */
@media (min-width: 1200px) and (max-width: 1399.98px) {
margin-top: 80px; /* ⬅️ DISTÂNCIA NO NOTEBOOK (antes 100px) */
}
/* Telas < 1200px */
@media (max-width: 1199.98px) {
margin-top: 24px;
}
@media (max-width: 768px) {
margin-top: 20px;
}
@media (max-width: 480px) {
margin-top: 16px;
}
margin: 0; /* remove aqueles 100px enormes de antes */
}
.footer-left p {
/* 🔁 PADRÃO PARA DESKTOPS E NOTEBOOKS: MESMO ESPAÇAMENTO HORIZONTAL */
margin: 0 0 4px 120px;
font-size: 14px;
font-weight: 600;
/* Telas < 1200px */
@media (max-width: 1199.98px) {
margin: 0 0 8px 0;
}
margin: 0 0 2px 0; /* menos espaçamento vertical */
font-size: 13px;
font-weight: 500;
@media (max-width: 768px) {
margin-left: 0;
font-size: 13px;
font-size: 12px;
}
@media (max-width: 480px) {
font-size: 12px;
font-size: 11.5px;
}
}
/* ===================================== */
/* LADO DIREITO */
/* LADO DIREITO (REDES + BOTÃO) */
/* ===================================== */
.footer-right {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 100px; /* ⬅️ DISTÂNCIA DO BLOCO DIREITO PRO TOPO (antes 100px) */
gap: 0px;
align-items: flex-end;
gap: 8px;
margin-right: 120px; /* espaçamento horizontal já ajustado */
/* 💻 NOTEBOOKS 12001399px */
@media (min-width: 1200px) and (max-width: 1399.98px) {
margin-top: 80px; /* ⬅️ MESMO IDEA DO ESQUERDO (antes 100px) */
gap: 10px;
}
/* Telas < 1200px */
@media (max-width: 1199.98px) {
margin-top: 20px;
margin-right: 0;
align-items: center;
gap: 10px;
}
@media (max-width: 768px) {
align-items: flex-start;
margin-top: 18px;
}
@media (max-width: 480px) {
margin-top: 16px;
}
}
@ -125,21 +82,14 @@
.social-wrapper {
width: 100%;
display: flex;
justify-content: flex-start;
margin-bottom: 0;
@media (min-width: 1200px) and (max-width: 1399.98px) {
margin-top: 4px;
}
justify-content: flex-end;
@media (max-width: 1199.98px) {
justify-content: center;
margin-top: 4px;
}
@media (max-width: 768px) {
justify-content: flex-start;
margin-top: 5px;
}
}
@ -147,7 +97,6 @@
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 0;
@media (max-width: 480px) {
gap: 6px;
@ -155,42 +104,41 @@
}
.social-label {
font-size: 14px;
font-size: 13px;
font-weight: 500;
@media (max-width: 480px) {
font-size: 13px;
font-size: 12px;
}
}
.social-icon i {
font-size: 22px;
font-size: 20px;
color: #FFF;
cursor: pointer;
transition: 0.2s;
transition: transform 0.15s ease, opacity 0.15s ease;
@media (max-width: 480px) {
font-size: 19px;
font-size: 18px;
}
}
.social-icon i:hover {
opacity: 0.7;
opacity: 0.8;
transform: translateY(-1px);
}
/* Botão */
.footer-button-wrapper {
margin-top: 0;
/* Botão Política de Privacidade */
@media (min-width: 1200px) and (max-width: 1399.98px) {
margin-top: 6px;
}
.footer-button-wrapper {
display: flex;
justify-content: flex-end;
@media (max-width: 1199.98px) {
margin-top: 6px;
justify-content: center;
}
@media (max-width: 768px) {
margin-top: 10px;
justify-content: flex-start;
}
}

View File

@ -1,10 +1,12 @@
<header class="header-container">
<header
class="header-container"
[class.header-scrolled]="isScrolled">
<div class="header-top">
<!-- LOGO + TÍTULO (CLICÁVEIS) -->
<a class="logo-area" routerLink="/"> <!-- ⬅️ AGORA É UM LINK ANGULAR -->
<img src="logo.jpg" alt="Logo" class="logo">
<img src="logo.png" alt="Logo" class="logo">
<div class="logo-text ms-2">
<span class="line">Line</span><span class="gestao">Gestão</span>
@ -21,15 +23,30 @@
<!-- BOTÕES -->
<div class="btn-area">
<button class="btn btn-cadastrar">Cadastre-se</button>
<button class="btn btn-login">Login</button>
<button
type="button"
class="btn btn-cadastrar"
[routerLink]="['/register']">
Cadastre-se
</button>
<button
type="button"
class="btn btn-login"
[routerLink]="['/login']">
Login
</button>
</div>
</div>
<!-- FAIXA AZUL -->
<div class="header-bar">
<span class="header-bar-text">Somos a escolha certa para estar sempre conectado!</span>
<!-- FAIXA AZUL SÓ NA HOME -->
<div class="header-bar" *ngIf="isHome">
<span class="header-bar-text">
Somos a escolha certa para estar sempre conectado!
</span>
</div>
</header>

View File

@ -2,13 +2,41 @@
/* HEADER PRINCIPAL */
/* ===================== */
.header-container {
position: sticky;
top: 0;
z-index: 1000;
width: 100%;
background: #ffffff;
/* mais transparente mesmo no topo */
background: rgba(255, 255, 255, 0.75);
font-family: 'Inter', sans-serif;
display: flex;
flex-direction: column;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
transition:
background-color 0.25s ease,
backdrop-filter 0.25s ease,
-webkit-backdrop-filter 0.25s ease,
box-shadow 0.25s ease,
border-color 0.25s ease;
}
/* HEADER “FOSCO” QUANDO SCROLLADO BEM TRANSPARENTE */
.header-container.header-scrolled {
background: rgba(255, 255, 255, 0.22); /* quase vidro puro */
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
border-bottom: 1px solid rgba(201, 30, 181, 0.3);
}
/* ===================== */
/* TOP AREA (LOGO + MENU + BOTÕES) */
/* ===================== */
@ -314,28 +342,39 @@
/* ===================== */
.header-bar {
width: 100%;
height: 38.41px;
background: rgba(3, 15, 170, 0.93);
height: 34px; /* um pouco mais baixa */
background: linear-gradient(90deg, #030FAA 0%, #6066FF 45%, #C91EB5 100%);
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid rgba(255, 255, 255, 0.25);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
@media (max-width: 480px) {
height: 34px;
height: 30px;
}
}
.header-bar-text {
color: #ffffff;
font-size: 16px;
font-weight: 700;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
/* NOTEBOOKS 12001399px */
@media (min-width: 1200px) and (max-width: 1399.98px) {
font-size: 15px;
}
/* “pill” para destacar o texto */
padding: 4px 14px;
border-radius: 999px;
background: rgba(0, 0, 0, 0.12);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
@media (max-width: 480px) {
font-size: 14px;
font-size: 11.5px;
padding: 3px 10px;
letter-spacing: 0.06em;
text-align: center;
}
}

View File

@ -1,11 +1,30 @@
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { Component, HostListener } from '@angular/core';
import { RouterLink, Router, NavigationEnd } from '@angular/router';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-header',
standalone: true, // ⬅️ importante para usar `imports` aqui
imports: [RouterLink], // ⬅️ habilita o uso de routerLink no template
standalone: true,
imports: [RouterLink, CommonModule], // ⬅️ CommonModule para usar *ngIf
templateUrl: './header.html',
styleUrl: './header.scss',
styleUrls: ['./header.scss'],
})
export class Header { }
export class Header {
isScrolled = false;
isHome = true; // valor inicial (ao abrir normalmente cai na home)
constructor(private router: Router) {
// escuta mudanças de rota para saber se está na home
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
const url = event.urlAfterRedirects || event.url;
this.isHome = (url === '/' || url === '');
}
});
}
@HostListener('window:scroll', [])
onWindowScroll() {
this.isScrolled = window.scrollY > 10; // passou 10px de scroll, ativa o “fosco”
}
}

View File

@ -50,9 +50,13 @@
<div class="row justify-content-center button-section">
<div class="col-auto">
<app-cta-button label="COMEÇAR AGORA" (clicked)="iniciar()"></app-cta-button>
<app-cta-button
label="COMEÇAR AGORA"
(clicked)="iniciar()">
</app-cta-button>
</div>
</div>
</div>

View File

@ -1,34 +1,29 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common'; // Importa CommonModule
import { CommonModule } from '@angular/common';
import { FeatureCardComponent } from '../../components/feature-card/feature-card';
import { CtaButtonComponent } from '../../components/cta-button/cta-button';
import { Router } from '@angular/router'; // ⬅️ IMPORT CERTO (ANGULAR)
@Component({
selector: 'app-home',
// 1. Marca o componente como autônomo
standalone: true,
// 2. Importa o que o template precisa:
imports: [
CommonModule, // Para usar diretivas comuns (ngClass, ngIf, etc.) no seu home.html
FeatureCardComponent, // Para usar o seletor <app-feature-card> no seu home.html
CommonModule,
FeatureCardComponent,
CtaButtonComponent
],
templateUrl: './home.html',
styleUrl: './home.scss',
styleUrls: ['./home.scss'], // ⬅️ styleUrls (array)
})
export class Home {
// Seus dados dos cards podem ser definidos aqui para uso no home.html:
/*
cardsData = [
{ title: 'Monitoramento Completo', iconClass: 'bi bi-laptop', description: '...' },
// ...
];
*/
// ⬅️ Aqui o Angular injeta o Router e guarda em this.router
constructor(private router: Router) {}
iniciar(): void {
console.log('Botão COMEÇAR AGORA clicado! Redirecionando ou abrindo modal...');
// Aqui você adicionaria sua lógica de navegação, como:
// this.router.navigate(['/cadastro']);
// Ou uma chamada para abrir um modal de registro.
// só pra debug, se quiser:
// console.log('Botão COMEÇAR AGORA clicado! Redirecionando para /register...');
this.router.navigate(['/register']); // ⬅️ agora this.router não é mais undefined
}
}

View File

@ -0,0 +1,49 @@
<div class="login-wrapper">
<div class="login-card shadow-sm">
<!-- Título -->
<div class="login-title mb-4">
<h2 class="mb-0">Login</h2>
</div>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<!-- Usuário -->
<div class="mb-3">
<label class="form-label">Usuário</label>
<input
type="text"
class="form-control"
formControlName="username"
placeholder="Usuário" />
<div class="text-danger small mt-1" *ngIf="hasError('username')">
Informe o usuário.
</div>
</div>
<!-- Senha -->
<div class="mb-4">
<label class="form-label">Senha</label>
<input
type="password"
class="form-control"
formControlName="password"
placeholder="Senha" />
<div class="text-danger small mt-1" *ngIf="hasError('password')">
A senha deve ter pelo menos 6 caracteres.
</div>
</div>
<!-- Botão Entrar -->
<button
type="submit"
class="btn btn-primary w-100 login-btn-submit"
[disabled]="isSubmitting">
{{ isSubmitting ? 'Entrando...' : 'ENTRAR' }}
</button>
</form>
</div>
</div>

View File

@ -0,0 +1,119 @@
/* ========================= */
/* TELA DE LOGIN */
/* ========================= */
/* Wrapper para centralizar o card entre header e footer */
.login-wrapper {
min-height: calc(100vh - 60px);
display: flex;
justify-content: center; /* login fica no centro */
align-items: center;
padding-top: 32px;
padding-right: 12px;
padding-bottom: 100px; /* mesmo “respiro” do cadastro */
padding-left: 12px;
background: url('../../../assets/wallpaper/registro_qualidade3.png')
no-repeat right center;
background-size: contain;
background-color: #efefef;
@media (max-width: 992px) {
padding-left: 16px;
padding-right: 16px;
padding-bottom: 80px;
}
@media (max-width: 576px) {
min-height: calc(100vh - 40px);
padding-top: 24px;
padding-bottom: 60px;
}
}
/* Card principal mesmo estilo glass do cadastro,
mas SEM margin-left e centralizado pelo flex */
.login-card {
background-color: transparent;
border-radius: 10px;
border: 2px solid #c91eb5;
max-width: 500px; /* aumenta a largura como no cadastro */
width: 100%;
min-height: 380px; /* um pouco menor que o cadastro, mas folgado */
padding: 28px 24px; /* mesmo “respiro” interno */
box-sizing: border-box;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
.mb-3,
.mb-4 {
margin-bottom: 0.9rem;
}
}
/* Título centralizado rosa */
.login-title {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.25rem !important;
h2 {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 700;
font-size: 32px;
color: #c91eb5;
margin: 0;
}
}
/* Labels mesmo tamanho do cadastro */
.form-label {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 500;
font-size: 14px;
color: #000000;
}
/* Inputs iguais aos do cadastro (borda azul, maiores) */
.form-control {
height: 38px; /* antes 34px */
border-radius: 8px;
border: 2px solid #6066ff;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 500;
font-size: 14px; /* antes 13px */
color: #000000;
&::placeholder {
color: rgba(0, 0, 0, 0.5);
}
}
/* Botão ENTRAR rosa sólido, mesmo estilo do cadastrar */
.login-btn-submit {
border-radius: 40px;
border: none;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 600;
font-size: 14px;
letter-spacing: 0.5px;
text-transform: uppercase;
padding: 9px 0;
background-color: #c91eb5;
color: #ffffff;
&:hover {
filter: brightness(1.04);
}
}
/* Mensagens de erro */
.text-danger.small {
font-size: 11px;
}

View File

@ -0,0 +1,63 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
FormBuilder,
FormGroup,
Validators,
ReactiveFormsModule
} from '@angular/forms';
@Component({
selector: 'app-login',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './login.html',
styleUrls: ['./login.scss']
})
export class LoginComponent {
loginForm: FormGroup;
isSubmitting = false;
constructor(private fb: FormBuilder) {
this.loginForm = this.fb.group({
username: ['', [Validators.required]], // “Usuário” do protótipo
password: ['', [Validators.required, Validators.minLength(6)]]
});
}
onSubmit(): void {
if (this.loginForm.invalid) {
this.loginForm.markAllAsTouched();
return;
}
this.isSubmitting = true;
// Simulação de envio preparado para API futura
console.log('Dados de login:', this.loginForm.value);
// FUTURO: aqui você injeta seu AuthService e chama a API .NET:
/*
this.authService.login(this.loginForm.value).subscribe({
next: () => { ... },
error: () => { ... },
complete: () => this.isSubmitting = false
});
*/
setTimeout(() => {
this.isSubmitting = false;
alert('Login enviado (simulado). Depois conectamos na API .NET 😉');
}, 800);
}
hasError(field: string, error?: string): boolean {
const control = this.loginForm.get(field);
if (!control) return false;
if (error) {
return control.touched && control.hasError(error);
}
return control.touched && control.invalid;
}
}

View File

@ -1 +1,108 @@
<p>register works!</p>
<!-- src/app/pages/register.component.html -->
<div class="register-wrapper">
<div class="register-card shadow-sm">
<!-- Barra / título -->
<div class="register-title mb-4">
<span class="register-icon">
<i class="bi bi-box-arrow-in-right"></i> <!-- ícone bootstrap -->
</span>
<h2 class="mb-0">Cadastre-se</h2>
</div>
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<!-- Nome completo -->
<div class="mb-3">
<label class="form-label">Nome Completo</label>
<input
type="text"
class="form-control"
formControlName="fullName"
placeholder="Nome Completo" />
<div class="text-danger small mt-1" *ngIf="hasError('fullName')">
Informe o seu nome completo (mínimo 3 caracteres).
</div>
</div>
<!-- E-mail -->
<div class="mb-3">
<label class="form-label">E-mail</label>
<input
type="email"
class="form-control"
formControlName="email"
placeholder="E-mail" />
<div class="text-danger small mt-1" *ngIf="hasError('email', 'required')">
E-mail é obrigatório.
</div>
<div class="text-danger small mt-1" *ngIf="hasError('email', 'email')">
Informe um e-mail válido.
</div>
</div>
<!-- Telefone -->
<div class="mb-3">
<label class="form-label">Telefone</label>
<input
type="tel"
class="form-control"
formControlName="phone"
placeholder="Telefone" />
<div class="text-danger small mt-1" *ngIf="hasError('phone')">
Informe um telefone válido.
</div>
</div>
<!-- Senha -->
<div class="mb-3">
<label class="form-label">Senha</label>
<input
type="password"
class="form-control"
formControlName="password"
placeholder="Senha" />
<div class="text-danger small mt-1" *ngIf="hasError('password')">
A senha deve ter pelo menos 6 caracteres.
</div>
</div>
<!-- Confirmar senha -->
<div class="mb-4">
<label class="form-label">Confirme sua senha</label>
<input
type="password"
class="form-control"
formControlName="confirmPassword"
placeholder="Confirme sua senha" />
<div class="text-danger small mt-1" *ngIf="registerForm.errors?.['passwordsMismatch'] && registerForm.touched">
As senhas não conferem.
</div>
</div>
<!-- Botão Cadastrar (gradiente) -->
<button
type="submit"
class="btn btn-primary w-100 register-btn mb-3"
[disabled]="isSubmitting">
{{ isSubmitting ? 'Cadastrando...' : 'CADASTRAR' }}
</button>
<!-- Botão para quem já tem conta -->
<button
type="button"
class="btn btn-outline-primary w-100 login-btn"
routerLink="/login">
JÁ TEM UMA CONTA? ENTRE
</button>
</form>
</div>
</div>

View File

@ -0,0 +1,155 @@
/* ========================= */
/* TELA DE CADASTRO */
/* ========================= */
/* Wrapper para centralizar o card entre header e footer */
.register-wrapper {
/* ocupa quase a tela toda, mas sem exagero */
min-height: calc(100vh - 60px);
display: flex;
justify-content: flex-start;
align-items: center;
/* respiro mais equilibrado */
padding-top: 32px;
padding-right: 12px;
padding-bottom: 100px; /* ainda empurra o footer, mas menos que 140 */
padding-left: 5vw;
background: url('../../../assets/wallpaper/registro_qualidade2.png')
no-repeat right center;
background-size: contain;
background-color: #efefef;
/* responsivo: em telas menores, centraliza o card e reduz paddings */
@media (max-width: 992px) {
justify-content: center;
padding-left: 16px;
padding-right: 16px;
padding-bottom: 80px;
}
@media (max-width: 576px) {
min-height: calc(100vh - 40px);
padding-top: 24px;
padding-bottom: 60px;
}
}
/* Card principal AGORA MAIS LARGO */
.register-card {
background-color: transparent;
border-radius: 10px;
border: 2px solid #c91eb5;
max-width: 500px; /* antes 340px ✅ aumenta a largura do card */
width: 100%;
min-height: 500px; /* leve ajuste pra não ficar “apertado” */
padding: 28px 24px; /* um pouco mais de “respiro” interno */
box-sizing: border-box;
margin-left: 150px;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
.mb-3,
.mb-4 {
margin-bottom: 0.9rem; /* mantém espaçamento confortável */
}
}
/* Título: ícone + texto lado a lado */
.register-title {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
margin-bottom: 1.25rem !important;
h2 {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 700;
font-size: 32px;
color: #c91eb5;
margin: 0;
}
}
/* Ícone ao lado do título */
.register-icon {
display: flex;
align-items: center;
justify-content: center;
color: #c91eb5;
i {
font-size: 32px;
}
}
/* Labels */
.form-label {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 500;
font-size: 14px; /* um pouco maior pra acompanhar o card */
color: #000000;
}
/* Inputs com borda azul do protótipo levemente maiores */
.form-control {
height: 38px; /* antes 34px */
border-radius: 8px;
border: 2px solid #6066ff;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 500;
font-size: 14px; /* antes 13px */
color: #000000;
&::placeholder {
color: rgba(0, 0, 0, 0.5);
}
}
/* Botão principal (CADASTRAR) rosa sólido */
.register-btn {
border-radius: 40px;
border: none;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 600;
font-size: 14px; /* um tic maior */
letter-spacing: 0.5px;
text-transform: uppercase;
padding: 9px 0;
background-color: #c91eb5;
color: #ffffff;
&:hover {
filter: brightness(1.04);
}
}
/* Botão "JÁ TEM UMA CONTA? ENTRE" azul sólido */
.login-btn {
border-radius: 40px;
border: none;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 600;
font-size: 13px;
letter-spacing: 0.5px;
text-transform: uppercase;
padding: 8px 0;
background-color: #030faa;
color: #ffffff;
&:hover {
filter: brightness(1.04);
}
}
/* Mensagens de erro */
.text-danger.small {
font-size: 11px;
}

View File

@ -1,11 +1,75 @@
// src/app/pages/register.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-register',
imports: [],
standalone: true,
imports: [CommonModule, ReactiveFormsModule, RouterLink],
templateUrl: './register.html',
styleUrl: './register.scss',
styleUrls: ['./register.scss'] // ou .scss
})
export class Register {
registerForm: FormGroup;
isSubmitting = false;
constructor(private fb: FormBuilder) {
this.registerForm = this.fb.group({
fullName: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
phone: ['', [Validators.required]],
password: ['', [Validators.required, Validators.minLength(6)]],
confirmPassword: ['', [Validators.required]]
}, {
validators: [this.passwordsMatchValidator]
});
}
// Validador para confirmar senha
private passwordsMatchValidator(group: FormGroup) {
const pass = group.get('password')?.value;
const confirm = group.get('confirmPassword')?.value;
return pass === confirm ? null : { passwordsMismatch: true };
}
onSubmit(): void {
if (this.registerForm.invalid) {
this.registerForm.markAllAsTouched();
return;
}
this.isSubmitting = true;
// Por enquanto: apenas exibe os dados e "simula" o envio
console.log('Dados de cadastro:', this.registerForm.value);
// TODO FUTURO: quando a API .NET estiver pronta,
// você injeta um AuthService e envia para o backend:
/*
this.authService.register(this.registerForm.value).subscribe({
next: () => { ... },
error: () => { ... },
complete: () => this.isSubmitting = false
});
*/
// 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 {
const control = this.registerForm.get(field);
if (!control) return false;
if (error) {
return control.touched && control.hasError(error);
}
return control.touched && control.invalid;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

View File

@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;700&display=swap" rel="stylesheet">
<!-- Dentro de index.html, na seção <head> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">