diff --git a/angular.json b/angular.json index 4d7fd76..f91ed77 100644 --- a/angular.json +++ b/angular.json @@ -96,5 +96,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/package.json b/package.json index d14c4d8..29b88ec 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@angular/platform-browser": "^16.1.0", "@angular/platform-browser-dynamic": "^16.1.0", "@angular/router": "^16.1.0", + "creditcardpayments": "^1.0.3", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.13.0" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 91ada6e..8802ab8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -2,11 +2,19 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { EscogerRutaComponent } from './components/escoger-ruta/escoger-ruta.component'; import { CrearVueloComponent } from './components/crear-vuelo/crear-vuelo.component'; +import { LoginComponent } from './components/login/login.component'; +import { HomeComponent } from './components/home/home.component'; +import { AsientosComponent } from './components/asientos/asientos.component'; +import { PromocionesComponent } from './components/promociones/promociones.component'; const routes: Routes = [ - {path:'inicio', component:EscogerRutaComponent}, + {path:'inicio', component:HomeComponent}, + {path:'buscar-vuelo', component:EscogerRutaComponent}, {path:'guardar-vuelo', component:CrearVueloComponent}, - {path:'**', component:EscogerRutaComponent}]; + {path:'login', component:LoginComponent}, + {path:'promociones', component:PromocionesComponent}, + {path:'**', component:HomeComponent}, + ]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/src/app/app.component.html b/src/app/app.component.html index a709aed..7dc820d 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,15 +1,3 @@ -
- - Inicio -
- Vuelos -
- Promociones - Sign out - ? -
-
diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9c1402f..c092f37 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,13 +8,22 @@ import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { CrearVueloComponent } from './components/crear-vuelo/crear-vuelo.component'; import { PieComponent } from './components/pie/pie.component'; +import { LoginComponent } from './components/login/login.component'; +import { AuthService } from './services/auth.service'; +import { HomeComponent } from './components/home/home.component'; +import { PromocionesComponent } from './components/promociones/promociones.component'; +import { AsientosComponent } from './components/asientos/asientos.component'; @NgModule({ declarations: [ AppComponent, EscogerRutaComponent, CrearVueloComponent, - PieComponent + PieComponent, + LoginComponent, + HomeComponent, + PromocionesComponent, + AsientosComponent ], imports: [ BrowserModule, @@ -22,7 +31,9 @@ import { PieComponent } from './components/pie/pie.component'; HttpClientModule, FormsModule ], - providers: [], + providers: [ + AuthService + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/components/asientos/asientos.component.css b/src/app/components/asientos/asientos.component.css new file mode 100644 index 0000000..49318ab --- /dev/null +++ b/src/app/components/asientos/asientos.component.css @@ -0,0 +1,262 @@ +.plane { + margin: 20px auto; + max-width: 450px; +} + +.cockpit { + height: 250px; + position: relative; + overflow: hidden; + text-align: center; + border-bottom: 5px solid #3a3939; + + &:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + height: 700px; + width: 98.2%; + border-radius: 60%; + border-right: 5px solid #3a3939; + border-left: 5px solid #3a3939; + } + + h1 { + width: 60%; + margin: 100px auto 35px auto; + } +} + +.fuselage { + border: 5px solid #3a3939; + border-top: 0px; +} + +ol { + list-style: none; + padding: 0; + margin: 0; +} + +.row { + --bs-gutter-x: 0rem; + --bs-gutter-y: 0; +} + +.seats { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; +} + +.seat { + display: flex; + height: 40px; + flex: 0 0 14.28571428571429%; + padding: 2px; + position: relative; + + &:nth-child(3) { + margin-right: 14.28571428571429%; + } + + input[type="checkbox"] { + position: absolute; + opacity: 0; + } + + input[type="checkbox"]:checked { + +label { + background: #bada55; + -webkit-animation-name: rubberBand; + animation-name: rubberBand; + animation-duration: 300ms; + animation-fill-mode: both; + } + } + + input[type="checkbox"]:disabled { + +label { + background: #dddddd; + text-indent: -9999px; + overflow: hidden; + + &:after { + content: "X"; + text-indent: 0; + position: absolute; + top: 4px; + left: 50%; + transform: translate(-50%, 0%); + } + + &:hover { + box-shadow: none; + cursor: not-allowed; + } + } + } + + .active { + background-color: green; + /* Estilo activado */ + } + + .inactive { + background-color: red; + /* Estilo desactivado */ + } + + label { + display: block; + position: relative; + width: 100%; + text-align: center; + font-size: 14px; + font-weight: bold; + line-height: 1.5rem; + padding: 2px 0; + border-radius: 5px; + animation-duration: 300ms; + animation-fill-mode: both; + + &:before { + content: ""; + position: absolute; + width: 75%; + height: 75%; + top: 1px; + left: 50%; + transform: translate(-50%, 0%); + background: rgb(255, 0, 0); + border-radius: 3px; + } + + &:hover { + cursor: pointer; + box-shadow: 0 0 0px 2px #00ff48; + } + } +} +h2 { + font-family: 'Hug'; + margin: 15px; + font-size: 12px; + text-align: center; + color: #fed809; +} + +h5 { + font-family: 'Hug'; + margin: 15px; + font-size: 13px; + text-align: center; + color: #f8770f; +} + +.btn-primary { + font-family: 'Asap'; + border-radius: 20px; + background-color: #fed809; + color: white; + align-items: center; + padding: 10px 20px; + cursor: pointer; + border: none; +} + +.btn-danger { + font-family: 'Asap'; + border-radius: 20px; + background-color: #e2097e; + color: white; + border: 3px #be8fa8; + padding: 10px 20px; + cursor: pointer; +} + +.btn-success { + font-family: 'Asap'; + border-radius: 20px; + background-color: white; + color: #e2097e; + border: 2px #e2097e; + padding: 10px 20px; + cursor: pointer; +} + +.modal-header { + align-items: center; +} + +.modal-title { + font-family: 'Hug'; + margin: 15px; + font-size: 13px; + margin-left: 20px; + color: #f8770f; + text-align: center; +} + +label { + margin: 2px; + display: inline-block; + font-family: 'gro'; + font-size: 15px; + +} + +/* Estilo para etiquetas con contenido del 1A al 5F (color rosado) */ +label[for^="1"], +label[for^="2"], +label[for^="3"], +label[for^="4"], +label[for^="5"] { + color: #063147; +} + +/* Estilo para etiquetas con contenido del 6A al 10F (color morado) */ +label[for^="6"], +label[for^="7"], +label[for^="8"], +label[for^="9"], +label[for^="10"] { + color: #203b06; +} + +/* Estilo para la leyenda de colores */ +.legend { + list-style-type: none; + padding: 0; + margin: 0; + text-align: center; +} + +/* Estilo para cada elemento de la leyenda */ +.legend li { + margin-bottom: 10px; +} + +/* Estilo para los cuadros de colores */ +.legend-color { + display: inline-block; + width: 20px; + height: 20px; + margin-right: 10px; + border: 1px solid #ffffff; + border-radius: 50%; + /* Borde alrededor de los cuadros de colores (opcional) */ +} + +/* Estilo para las imágenes de los asientos */ +.plane img { + position: absolute; + max-width: 40px; + max-height: 40px; + margin-left: 15px; + margin-bottom: 15px; + z-index: -1; +} \ No newline at end of file diff --git a/src/app/components/asientos/asientos.component.html b/src/app/components/asientos/asientos.component.html new file mode 100644 index 0000000..ae87885 --- /dev/null +++ b/src/app/components/asientos/asientos.component.html @@ -0,0 +1,61 @@ + +
+ + + Inicio +
+ Crear vuelo +
+ Promociones + Log out +
+ +
+




+
+ + + + +
+ \ No newline at end of file diff --git a/src/app/components/asientos/asientos.component.spec.ts b/src/app/components/asientos/asientos.component.spec.ts new file mode 100644 index 0000000..52ce553 --- /dev/null +++ b/src/app/components/asientos/asientos.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AsientosComponent } from './asientos.component'; + +describe('AsientosComponent', () => { + let component: AsientosComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AsientosComponent] + }); + fixture = TestBed.createComponent(AsientosComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/asientos/asientos.component.ts b/src/app/components/asientos/asientos.component.ts new file mode 100644 index 0000000..0ce932a --- /dev/null +++ b/src/app/components/asientos/asientos.component.ts @@ -0,0 +1,198 @@ +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; + +@Component({ + selector: 'app-asientos', + templateUrl: './asientos.component.html', + styleUrls: ['./asientos.component.css'] +}) +export class AsientosComponent implements OnInit{ + rows: any[] = []; + disabledCheckboxes: string[] = []; + selected: number = 0; + isRandomActive = false; + public eventListenersAttached: boolean = false; + @Input() maxSelectedCheckboxes: number = 1; + @ViewChild('seatsContainer') seatsContainer!: ElementRef; + @Output() emitArrayAsientos = new EventEmitter(); + allCheckboxes: HTMLInputElement[] = []; + habilitadoImageSrc: string = '../assets/habilitado.jpg'; + desHabilitadoImageSrc: string = '../assets/desHabilitado.jpg'; + + constructor(private el: ElementRef) { + // Agrega las filas y asientos a la estructura + for (let rowNumber = 1; rowNumber <= 9; rowNumber++) { + const seats = ['A', 'B', 'C', 'D', 'E', 'F']; // Asientos de A a F por fila + const row = { + rowNumber: rowNumber, + seats: seats.map(seat => ({ + id: `${rowNumber}${seat}`, + selected: true, + enabled: true, // Inicialmente, todos los asientos están habilitados + imageSrc: this.habilitadoImageSrc // Inicialmente, se muestra la imagen habilitada + })) + }; + this.rows.push(row); + } + console.log(this.rows); + } + ngOnInit(): void { + this.disableCheckboxes(); + } + + toggleSeatState(seat: any) { + seat.enabled = !seat.enabled; + seat.imageSrc = seat.enabled ? this.habilitadoImageSrc : this.desHabilitadoImageSrc; + this.rows.forEach((row) => { + row.seats.forEach((seat: any) => { + seat.selected = false; + }); + }); + } + + toggleRandom(): void { + this.isRandomActive = !this.isRandomActive; + + if (!this.isRandomActive) { + this.selectedCheckboxes = []; + this.selected = 0; + const checkboxes = document.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach((checkbox) => { + if (checkbox instanceof HTMLInputElement) { + checkbox.checked = false; + } + }); + this.rows.forEach((row) => { + row.seats.forEach((seat: any) => { + seat.selected = false; + }); + }); + this.emitArrayAsientos.emit(this.selectedCheckboxes); + console.log(this.selectedCheckboxes); + for (const row of this.rows) { + for (const seat of row.seats) { + if (seat.selected) { + seat.enabled = !seat.enabled; + seat.imageSrc = seat.enabled ? this.habilitadoImageSrc : this.desHabilitadoImageSrc; + } + } + } + } + + if (this.isRandomActive) { + // Aquí puedes llamar a la función para seleccionar aleatoriamente los checkboxes + this.selectedCheckboxes = []; + this.selected = 0; + const checkboxes = document.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach((checkbox) => { + if (checkbox instanceof HTMLInputElement) { + checkbox.checked = false; + } + }); + this.rows.forEach((row) => { + row.seats.forEach((seat: any) => { + seat.selected = false; + }); + }); + this.selectRandomCheckboxes(); + this.emitArrayAsientos.emit(this.selectedCheckboxes); + console.log(this.selectedCheckboxes); + for (const row of this.rows) { + for (const seat of row.seats) { + if (seat.selected) { + seat.enabled = !seat.enabled; + seat.imageSrc = seat.enabled ? this.habilitadoImageSrc : this.desHabilitadoImageSrc; + } + } + } + } + } + + selectedCheckboxes: string[] = []; + + extractSelectedCheckboxes() { + this.selectedCheckboxes = []; + for (const row of this.rows) { + for (const seat of row.seats) { + if (seat.selected) { + this.selectedCheckboxes.push(seat.id); + } + } + } + } + + handleCheckboxChange(event: Event) { + const checkbox = event.target as HTMLInputElement; + const rowIdMatches = checkbox.id.match(/^(\d+)/); + if (rowIdMatches) { + const rowId = Number(rowIdMatches[1]) - 1; + if (rowId >= 0 && rowId <= this.rows.length) { + const row = this.rows[rowId].seats; + const selectedSeat = row.find((seat: any) => seat.id === checkbox.id); + + if (selectedSeat) { + selectedSeat.selected = checkbox.checked; + this.extractSelectedCheckboxes(); + } + + if (checkbox.checked) { + if (this.selectedCheckboxes.length <= this.maxSelectedCheckboxes) { + this.selected++; + if (this.selectedCheckboxes.length === this.maxSelectedCheckboxes) { + } + } else { + checkbox.checked = false; + } + } else { + this.selected--; + } + } else { + console.log('Row ID fuera de límites:', rowId); + } + } + } + + disableCheckboxes() { + const checkboxes: NodeListOf = this.el.nativeElement.querySelectorAll( + 'input[type="checkbox"]' + ); + + checkboxes.forEach((checkbox) => { + if (this.disabledCheckboxes.includes(checkbox.id)) { + checkbox.disabled = true; + } else { + checkbox.disabled = false; + } + }); + } + +// Selecciona aleatoriamente 'count' checkboxes de la matriz de checkboxes disponibles +selectRandomCheckboxes() { + const checkboxes = Array.from(document.querySelectorAll('input[type="checkbox"]:not(:disabled)')) as HTMLInputElement[]; + const availableCheckboxes = checkboxes.filter((checkbox) => !this.selectedCheckboxes.includes(checkbox.id)); + let count = this.maxSelectedCheckboxes - this.selectedCheckboxes.length; + + while (count > 0 && availableCheckboxes.length > 0) { + const randomIndex = Math.floor(Math.random() * availableCheckboxes.length); + const randomCheckbox = availableCheckboxes.splice(randomIndex, 1)[0]; + randomCheckbox.checked = true; + + // Simular un evento 'change' en el checkbox seleccionado aleatoriamente + const event = new Event('change', { bubbles: true }); + Object.defineProperty(event, 'target', { value: randomCheckbox, enumerable: true }); + + this.handleCheckboxChange(event); + + count--; + } +} + + displayStyle = "none"; + + openPopup() { + this.displayStyle = "block"; + } + closePopup() { + this.emitArrayAsientos.emit(this.selectedCheckboxes); + this.displayStyle = "none"; + } +} diff --git a/src/app/components/crear-vuelo/crear-vuelo.component.css b/src/app/components/crear-vuelo/crear-vuelo.component.css index 945508c..8bc094c 100644 --- a/src/app/components/crear-vuelo/crear-vuelo.component.css +++ b/src/app/components/crear-vuelo/crear-vuelo.component.css @@ -1,127 +1,80 @@ -.formulario { - height: auto; - padding-bottom: 50px; - padding-top: 150px; - flex-direction: column; - justify-content: center; - align-items: center; - display: flex; +* { + box-sizing: border-box; + font-family: 'Segoe UI'; } -.titulo { - color: #000000; - font-size: 25px; - font-family: Nunito Sans; - text-shadow: 3px 3px 5px rgba(126, 138, 162, 0.5); - padding-bottom: 50px; -} - -.detalles { - width: 400px; - height: auto; - flex-direction: column; - justify-content: center; - display: flex; - align-items: center; - gap: 10px; -} -.seccion{ - flex-direction: row; - justify-content: center; - display: flex; - align-items: center; - gap: 10px; - padding-bottom: 20px; -} -.informacion { - width: 250px; - font-family: Nunito Sans; - color: black; - align-content: center; - font-size: 18px; - text-align: left; -} - -.atributo { - align-self: stretch; - height: 40px; - padding: 0px 20px; - width: 220px; - background: white; - border: 0.50px #8CB4F1 solid; - justify-content: space-between; - align-items: center; - gap: 10px; - display: inline-flex; -} - -.texto, -.atributo input[type="number"], -.nombreAerolinea, -.origen, -.destino, -.horaSalida, -.horaLlegada { - width: 220px; - color: #555; - border: none; - font-size: 18px; - font-family: Poppins; - background: transparent; - word-wrap: break-word; - outline: none; -} - -.fechaSalida { - width: 200px; - border: none; - color: #555; - font-size: 16px; - outline: none; -} - -.textoFecha { - width: 220px; - color: #555; - border: none; - font-size: 18px; - font-family: Poppins; - background: transparent; - word-wrap: break-word; - outline: none; -} .boton { - margin-top: 50px; - width: 515px; + margin-top: 20px; + width: 490px; color: white; font-size: 18px; font-family: Poppins; font-weight: 600; word-wrap: break-word } +.informacion-vuelos h2 { + text-align: center; + } + -.alert { - padding: 10px; - margin-top: 0px auto; - margin-top: 10px; - width: 515px; - background-color: #d4edda; - color: #155724; - border: 1px solid #c3e6cb; - border-radius: 4px; - display: flex; - justify-content: center; - align-items: center; +/* Agrega esta clase para el botón de edición en azul */ +.editar-btn-blue { + background-color: blue; + color: white; + border: none; + width: 70px; + padding: 8px 12px; text-align: center; - animation: fadeOut 3s ease-in-out; /* Animación para desvanecerse */ + display: block; + margin: 0 auto; + cursor: pointer; + transition: background-color 0.3s ease; + margin-top: 10px; +} + +.editar-btn-blue:hover { + background-color: darkblue; } +.informacion-vuelos { + margin: 0 auto; + max-width: 40%; /* Puedes ajustar este porcentaje según prefieras */ + overflow-y: auto; /* Agrega un scroll vertical si la altura del contenido es mayor que un máximo */ + max-height: 600px; /* Puedes ajustar esta altura según prefieras */ + border: 5px solid #F23B88; /* Borde del recuadro */ + padding: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Sombra opcional para darle profundidad */ + } + -@keyframes fadeOut { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } +.eliminar-btn { + display: block; + margin: 10px auto; + background-color: red; + color: white; + border: none; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s; } +.eliminar-btn:hover { + background-color: darkred; +} + +.eliminar-btn { + color: white; + background-color: red; + border: none; + padding: 8px 12px; + text-align: center; + display: block; + margin: 0 auto; + cursor: pointer; + transition: background-color 0.3s ease; + } + + .eliminar-btn:hover { + background-color: darkred; + } + diff --git a/src/app/components/crear-vuelo/crear-vuelo.component.html b/src/app/components/crear-vuelo/crear-vuelo.component.html index 2e7a76d..5f30080 100644 --- a/src/app/components/crear-vuelo/crear-vuelo.component.html +++ b/src/app/components/crear-vuelo/crear-vuelo.component.html @@ -5,11 +5,10 @@ Inicio
- Vuelos + Crear vuelo
Promociones - Sign out - ? + Log out -
-
Datos del nuevo vuelo
-
-
-
Nombre de la aereolínea:
-
- +
+
+ +
Datos del nuevo vuelo
+
+
Nombre de la aereolínea:
+
+ +
-
-
-
Lugar de origen:
-
- +
+
Lugar de origen:
+
+ +
-
-
-
Lugar de destino:
-
- +
+
Lugar de destino:
+
+ +
-
-
-
Fecha de salida:
-
- +
+
Fecha de salida:
+
+ +
-
-
-
Hora de salida:
-
- +
+
Hora de salida:
+
+ +
-
-
-
Duración en minutos:
-
- +
+
Duración en minutos:
+
+ +
-
-
-
Precio en dólares:
-
- +
+
Precio en dólares:
+
+ +
-
-
-
Costo adicional por maleta extra:
-
- +
+
Clase:
+
+ +
+ + + + + + + + + +
+ El vuelo ha sido creado exitosamente.
-
- + + +
+ El vuelo ha sido actualizado exitosamente.
- +
+ + + + + +
+

Vuelos Creados

+
+ Aerolínea: {{ vuelo.nombreAerolinea }}
+ Origen: {{ vuelo.origen }}
+ Destino: {{ vuelo.destino }}
+ Fecha de Salida: {{ vuelo.fechaSalida }}
+ Hora de Salida: {{ vuelo.horaSalida }}
+ Duración: {{ vuelo.duracionVuelo }} minutos
+ Precio: ${{ vuelo.precio }}
+ Clase: {{ vuelo.clase === 'Turista' ? 'Turista' : 'Primera clase' }}
+ Número de Vuelo: {{ vuelo.numeroVuelo }}
+ + + + + +
+
+
+
+ \ No newline at end of file diff --git a/src/app/components/crear-vuelo/crear-vuelo.component.ts b/src/app/components/crear-vuelo/crear-vuelo.component.ts index 566fe8e..8bf845e 100644 --- a/src/app/components/crear-vuelo/crear-vuelo.component.ts +++ b/src/app/components/crear-vuelo/crear-vuelo.component.ts @@ -1,72 +1,241 @@ -import { Component, ElementRef, Renderer2 } from '@angular/core'; +import { Component, OnInit, ElementRef, Renderer2 } from '@angular/core'; import { NgForm } from '@angular/forms'; +import { Pasajero } from 'src/app/models/pasajero'; import { Vuelo } from 'src/app/models/vuelo'; import { Global } from 'src/app/services/global'; import { VuelosService } from 'src/app/services/vuelo.service'; +import { Router } from '@angular/router'; // Asegúrate de importar el Router +import { ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-crear-vuelo', templateUrl: './crear-vuelo.component.html', styleUrls: ['./crear-vuelo.component.css'], - providers: [VuelosService] + providers: [VuelosService], }) -export class CrearVueloComponent { +export class CrearVueloComponent implements OnInit { public horas: number[] = []; public titulo: string; public vuelo!: Vuelo; - //public vueloGuardar: Vuelo; + public vuelos: Vuelo[] = []; // Nuevo public url: string; public status: string; - //public idGuardado: string; public contadorVuelos!: number; - constructor(private renderer: Renderer2, private el: ElementRef, private _vueloService: VuelosService) { + public pasajeros?: Pasajero[]; + constructor( + private cdr: ChangeDetectorRef, + private renderer: Renderer2, + private el: ElementRef, + private _vueloService: VuelosService, + private router: Router // Agrega esta línea + ) { for (let i = 1; i <= 24; i++) { this.horas.push(i); + this.vuelo = new Vuelo( + '', + 0, + '', + '', + '', + '', + '', + 1, + '1', + [], + 65, + '', + 30, + 'Confirmado', + true + ); } - this.titulo = "GUARDAR"; + this.titulo = 'GUARDAR'; this.url = Global.url; - this.status = ""; - this._vueloService.getUltimoNumeroVuelo().subscribe( - ultimoNumero => { - this.contadorVuelos = ultimoNumero + 1; // Inicializa el contador con el último número + 1 - this.vuelo = new Vuelo('', this.contadorVuelos, '', '', '', '', '', 1, '1', [], 65, '', true); + this.status = ''; + this.pasajeros = []; + } + + ngOnInit() { + this.cargarVuelos(); + + if (this.vuelos && this.vuelos.length > 0) { + const maxNumeroVuelo = Math.max( + ...this.vuelos.map((v) => v.numeroVuelo || 0) + ); + this.contadorVuelos = maxNumeroVuelo + 1; + } else { + this.contadorVuelos = 1; + } + } + + cargarVuelos() { + this._vueloService.getVuelos().subscribe( + (response) => { + this.vuelos = response.vuelos; }, - error => { - console.log("Error al obtener el último número de vuelo:", error); - this.contadorVuelos = 1; + (error) => { + console.error('Error al obtener los vuelos:', error); + } ); - console.log(this.vuelo); } + public showSuccessAlert: boolean = false; + + mensajeGuardado: boolean = false; + guardarVuelo(form: NgForm) { - this._vueloService.guardarVuelo(this.vuelo).subscribe( - response => { + this.incrementarContadorVuelos(); + + const vueloToSend = this.getCleanVuelo(); + + if (!vueloToSend._id) { + vueloToSend._id = ''; // Asegurarte de que _id no sea undefined. + } + // Si hay otros campos que podrían ser undefined, inicialízalos aquí. + + this._vueloService.guardarVuelo(vueloToSend as Vuelo).subscribe( + (response) => { if (response.vuelo) { this.status = 'success'; - this.showSuccessAlert = true; // Mostrar la alerta de éxito - this.incrementarContadorVuelos(); - + this.mensajeGuardado = true; setTimeout(() => { - this.showSuccessAlert = false; // Ocultar la alerta después de 3 segundos - }, 3000); // 3000 milisegundos = 3 segundos - - console.log(response.vuelo._id); + this.mensajeGuardado = false; + }, 3000); + this.cargarVuelos(); + + // Restablecer el modelo vuelo a su estado inicial + this.vuelo = new Vuelo('', 0, '', '', '', '', '', 1, '1', [], 65, '', 30, 'Confirmado', true); + + // Limpiar el formulario //form.reset(); } else { this.status = 'failed'; - console.log("casi"); } }, - error => { + (error) => { console.log(error); } ); } + + + mensajeActualizado: boolean = false; + + confirmarActualizacion() { + if (this.vuelo._id) { + this._vueloService.actualizarVuelo(this.vuelo).subscribe( + (response) => { + console.log(response); + this.mensajeActualizado = true; + setTimeout(() => (this.mensajeActualizado = false), 3000); // Oculta el mensaje después de 3 segundos + this.cargarVuelos(); + + // Restablece el objeto vuelo después de actualizar + this.vuelo = new Vuelo( + '', + 0, + '', + '', + '', + '', + '', + 1, + '1', + [], + 65, + '', + 30, + 'Confirmado', + true + ); + }, + (error) => { + console.error('Error al actualizar el vuelo:', error); + } + ); + } else { + console.error('No hay ID del vuelo para actualizar.'); + } + } + + /* + guardarOEditarVuelo() { + if (this.vuelo._id) { + // Actualizar el vuelo + this._vueloService.actualizarVuelo(this.vuelo).subscribe( + (response) => { + console.log(response); + this.mensajeActualizado = true; + setTimeout(() => (this.mensajeActualizado = false), 3000); // Oculta el mensaje después de 3 segundos + this.cargarVuelos(); + }, + (error) => { + console.error('Error al actualizar el vuelo:', error); + } + ); + } else { + // Guardar un nuevo vuelo + this._vueloService.guardarVuelo(this.vuelo).subscribe( + (response) => { + console.log(response); + this.mensajeGuardado = true; + setTimeout(() => (this.mensajeGuardado = false), 3000); // Oculta el mensaje después de 3 segundos + this.cargarVuelos(); + }, + (error) => { + console.error('Error al guardar el vuelo:', error); + } + ); + } + } +*/ + getCleanVuelo(): Partial { + const { pasajeros, ...rest } = this.vuelo; + return rest; + } + + eliminarVuelo(id: string) { + console.log('ID del vuelo a eliminar:', id); + + this._vueloService.eliminarVuelo(id).subscribe( + (response) => { + console.log('Vuelo eliminado con éxito', response); + this.cargarVuelos(); // Actualizar la lista de vuelos después de eliminar uno + }, + (error) => { + console.error('Error al eliminar el vuelo:', error); + + } + ); + } + + editarVuelo(id: string) { + console.log('Función editarVuelo llamada con id:', id); + const userConfirmed = confirm('¿Seguro quieres editar el vuelo?'); + + let vueloEditar = this.vuelos.find((vuelo) => vuelo._id === id); + console.log('Datos del vuelo encontrado:', vueloEditar); // <-- Aquí está la línea agregada + + if (vueloEditar) { + this.vuelo = JSON.parse(JSON.stringify(vueloEditar)); // <-- Aquí haces la copia profunda + this.cdr.detectChanges(); // esto forzará la detección de cambios + } + window.location.hash = 'formVuelo'; + } + incrementarContadorVuelos() { this.contadorVuelos++; - this.vuelo = new Vuelo('', this.contadorVuelos, '', '', '', '', '', 1, '1', [], 65, '', true); + // this.vuelo = new Vuelo('', this.contadorVuelos, '', '', '', '', '', 1, '1', [], 65, '', 30, '', true); + this.vuelo.numeroVuelo = this.contadorVuelos; } + confirmarEliminacion(vueloId: string) { + const respuesta = window.confirm('¿Seguro que quieres eliminar el vuelo?'); + if (respuesta) { + this.eliminarVuelo(vueloId); + } + } + getCurrentDate(): string { const today = new Date(); const year = today.getFullYear(); @@ -74,4 +243,4 @@ export class CrearVueloComponent { const day = (today.getDate() + 1).toString().padStart(2, '0'); return `${year}-${month}-${day}`; } -} +} \ No newline at end of file diff --git a/src/app/components/escoger-ruta.rar b/src/app/components/escoger-ruta.rar new file mode 100644 index 0000000..a7959cc Binary files /dev/null and b/src/app/components/escoger-ruta.rar differ diff --git a/src/app/components/escoger-ruta/escoger-ruta.component.css b/src/app/components/escoger-ruta/escoger-ruta.component.css index f6a60f3..f453c88 100644 --- a/src/app/components/escoger-ruta/escoger-ruta.component.css +++ b/src/app/components/escoger-ruta/escoger-ruta.component.css @@ -1,16 +1,7 @@ -.fondo { - position: absolute; - z-index: -1; - padding-top: 100px; - left: 50%; - /* Centra horizontalmente */ - transform: translateX(-50%); - width: 900px; - height: auto; - display: flex; - margin: 0 auto; +* { + box-sizing: border-box; + font-family: 'Segoe UI'; } - .ruta, .carrito, .resumen, @@ -18,6 +9,7 @@ width: auto; height: auto; padding-top: 150px; + min-height: 450px; justify-content: center; align-items: center; flex-direction: column; @@ -94,8 +86,6 @@ width: 220px; color: #555; border: none; - font-size: 18px; - font-family: Poppins; background: transparent; word-wrap: break-word; outline: none; @@ -103,7 +93,7 @@ .informacionRuta .item, .carrito .item { - width: 192px; + width: 180px; background: transparent; padding-left: 0px; border: none; @@ -171,7 +161,8 @@ .mostrarVuelos{ width: auto; height: auto; - padding: 10px 10px 10px 10px; + padding: 10px; + margin-bottom: 40px; background: rgb(126, 138, 162); flex-direction: column; justify-content: center; @@ -183,6 +174,7 @@ .vuelo { width: 1000px; + height: auto; padding-left: 16px; padding-right: 16px; padding-top: 8px; @@ -197,6 +189,7 @@ .infoVuelo { align-self: stretch; + height: 30px; align-items: flex-start; gap: 3px; display: inline-flex; @@ -223,13 +216,67 @@ .mostrarVuelos .boton { width: 180px; + height: 30px; + margin-top: -17px; background: #05a72a; } +.mostrarVuelos .boton:hover, +.mostrarVuelos .boton:active { + background: #52db72; +} + +.mostrarVuelos .disabled-button { + background-color: #bdc3c7; +} +.mostrarVuelos .disabled-button:hover { + background-color: #bdc3c7; +} +.escogerRuta .alert { + background-color: #ec3030; + color: #cbe4d2; + border: 1px solid #57c7c7; + flex-direction: column; + font-size: 15px; + animation: fadeOut 6s ease-in-out; +} +/*alerta seleccion vuelo*/ +.mostrarVuelos .alert { + background-color: #d4edda; + color: #212b24; + border: 1px solid #57c7c7; + flex-direction: column; + font-size: 15px; + animation: fadeOut 20s ease-in-out; +} + +@keyframes fadeOut { + 0% { + opacity: 1; + } + 80% { + opacity: 0.5; + } + 100% { + opacity: 0; + } +} +/*pasajero*/ +.boton { + width: 490px; +} +.formulario { + padding-bottom: 30px; + padding-top: 30px; +} /*carrito*/ .carrito{ width: 630px; } +.a{ + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); + padding-top: -35px; +} .cart-button { width: 100px; height: 100px; @@ -269,7 +316,8 @@ .exit-button { padding: 5px 10px; - background-color: #ccc; + background-color: #ff0000; + color: white; border: none; border-radius: 5px; cursor: pointer; @@ -298,4 +346,269 @@ } .resumenBoton .boton { width: 600px; + margin-top: -300px; +} + +/*ASIENTO*/ +.plane { + margin: 20px auto; + max-width: 450px; +} + +.cockpit { + height: 250px; + position: relative; + overflow: hidden; + text-align: center; + border-bottom: 5px solid #3a3939; + + &:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + height: 700px; + width: 98.2%; + border-radius: 60%; + border-right: 5px solid #3a3939; + border-left: 5px solid #3a3939; + } + + h1 { + width: 60%; + margin: 100px auto 35px auto; + } +} + +.fuselage { + border: 5px solid #3a3939; + border-top: 0px; +} + +ol { + list-style: none; + padding: 0; + margin: 0; +} + +.row { + --bs-gutter-x: 0rem; + --bs-gutter-y: 0; +} + +.seats { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; +} + +.seat { + display: flex; + height: 40px; + flex: 0 0 14.28571428571429%; + padding: 2px; + position: relative; + + &:nth-child(3) { + margin-right: 14.28571428571429%; + } + + input[type="checkbox"] { + position: absolute; + opacity: 0; + } + + input[type="checkbox"]:checked { + +label { + background: #bada55; + -webkit-animation-name: rubberBand; + animation-name: rubberBand; + animation-duration: 300ms; + animation-fill-mode: both; + } + } + + input[type="checkbox"]:disabled { + +label { + background: #dddddd; + text-indent: -9999px; + overflow: hidden; + + &:after { + content: "X"; + text-indent: 0; + position: absolute; + top: 4px; + left: 50%; + transform: translate(-50%, 0%); + } + + &:hover { + box-shadow: none; + cursor: not-allowed; + } + } + } + + .active { + background-color: green; + /* Estilo activado */ + } + + .inactive { + background-color: red; + /* Estilo desactivado */ + } + + label { + display: block; + position: relative; + width: 100%; + text-align: center; + font-size: 14px; + font-weight: bold; + line-height: 1.5rem; + padding: 2px 0; + border-radius: 5px; + animation-duration: 300ms; + animation-fill-mode: both; + + &:before { + content: ""; + position: absolute; + width: 75%; + height: 75%; + top: 1px; + left: 50%; + transform: translate(-50%, 0%); + background: rgb(255, 0, 0); + border-radius: 3px; + } + + &:hover { + cursor: pointer; + box-shadow: 0 0 0px 2px #00ff48; + } + } +} +h2 { + font-family: 'Hug'; + margin: 15px; + font-size: 12px; + text-align: center; + color: #fed809; +} + +h5 { + font-family: 'Hug'; + margin: 15px; + font-size: 13px; + text-align: center; + color: #f8770f; +} + +.btn-primary { + font-family: 'Asap'; + border-radius: 20px; + background-color: #fed809; + color: white; + align-items: center; + padding: 10px 20px; + cursor: pointer; + border: none; +} + +.btn-danger { + font-family: 'Asap'; + border-radius: 20px; + background-color: #e2097e; + color: white; + border: 3px #be8fa8; + padding: 10px 20px; + cursor: pointer; +} + +.btn-success { + font-family: 'Asap'; + border-radius: 20px; + background-color: white; + color: #e2097e; + border: 2px #e2097e; + padding: 10px 20px; + cursor: pointer; +} + +.modal-header { + align-items: center; +} + +.modal-title { + font-family: 'Hug'; + margin: 15px; + font-size: 13px; + margin-left: 20px; + color: #f8770f; + text-align: center; +} + +label { + margin: 2px; + display: inline-block; + font-family: 'gro'; + font-size: 15px; + +} + +/* Estilo para etiquetas con contenido del 1A al 5F (color rosado) */ +label[for^="1"], +label[for^="2"], +label[for^="3"], +label[for^="4"], +label[for^="5"] { + color: #063147; +} + +/* Estilo para etiquetas con contenido del 6A al 10F (color morado) */ +label[for^="6"], +label[for^="7"], +label[for^="8"], +label[for^="9"], +label[for^="10"] { + color: #203b06; +} + +/* Estilo para la leyenda de colores */ +.legend { + list-style-type: none; + padding: 0; + margin: 0; + text-align: center; +} + +/* Estilo para cada elemento de la leyenda */ +.legend li { + margin-bottom: 10px; +} + +/* Estilo para los cuadros de colores */ +.legend-color { + display: inline-block; + width: 20px; + height: 20px; + margin-right: 10px; + border: 1px solid #ffffff; + border-radius: 50%; + /* Borde alrededor de los cuadros de colores (opcional) */ +} + +/* Estilo para las imágenes de los asientos */ +.plane img { + position: absolute; + max-width: 40px; + max-height: 40px; + margin-left: 15px; + margin-bottom: 15px; + z-index: -1; } \ No newline at end of file diff --git a/src/app/components/escoger-ruta/escoger-ruta.component.html b/src/app/components/escoger-ruta/escoger-ruta.component.html index 652021f..01ef3c8 100644 --- a/src/app/components/escoger-ruta/escoger-ruta.component.html +++ b/src/app/components/escoger-ruta/escoger-ruta.component.html @@ -11,14 +11,13 @@
+ Inicio - Vuelos - Promociones - Sign out - ? + Promociones + Login
- +
-
Busca tu vuelo:
-
+
- @@ -72,7 +73,7 @@
- @@ -87,14 +88,26 @@
- + +
+
+
- +
+
+
+ No existen vuelos para esa ruta en esa fecha
+
@@ -136,6 +149,7 @@
+
Selecciona tu vuelo:
@@ -148,7 +162,7 @@
-
  • +
  • {{vuelo.nombreAerolinea}}
    @@ -159,40 +173,248 @@
    ${{vuelo.precio}}
    +
    +
    + Puede añadir maletas adicionales por cada pasajero(2 maletas de mano y una de 23 kilos) +

    +
    + La maleta de 23 kilos (costo 65 dólares)

    +
    + La maleta de mano (costo 10 dólares)

    +
    + No puede escoger asientos.

    +
    +
    +
    + Puede añadir maletas adicionales por cada pasajero(5 maletas de mano y una de 23 kilos) +

    +
    + La maleta de 23 kilos (costo 65 dólares)

    +
    + La maleta de mano (costo 10 dólares)

    +
    + Si puede escoger asientos.

    +
  • -
    - -
    -
    - + +
    +
    +
    +
    Información del pasajero
    + + + + + + + + + +
    +
    Nombres completos
    +
    + +
    +
    +
    +
    Apellidos completos
    +
    + +
    +
    +
    +
    Fecha de nacimiento:
    +
    + +
    +
    +
    +
    Cédula o pasaporte:
    +
    + +
    +
    +
    + +
    +
    +
    -
    -
    Vuelos reservados
    + +
    +
    +
    +
    Información de contacto
    +
    +
    Nombre y apellido
    +
    + +
    +
    +
    +
    Correo electronico
    +
    + +
    +
    +
    +
    Cédula o pasaporte:
    +
    + +
    +
    +
    +
    Teléfono celular
    +
    + +
    +
    +
    + +
    +
    +
    -
  • -
    -
    {{vuelo.nombreAerolinea}}
    -
    {{vuelo.origen}} -> {{vuelo.destino}}
    -
    {{vuelo.fechaSalida}}
    + +
    +
    +
    +
    Informacion de los vuelos reservados
    +
    +
  • +
    +
    {{vuelo.origen}}, {{vuelo.destino}}-Ecuador

    +
    Salida: {{vuelo.fechaSalida}} a las {{vuelo.horaSalida}}:00h

    +
    Duración del viaje: {{vuelo.duracionVuelo}} minutos

    +
    Operado por: {{vuelo.nombreAerolinea}}

    +
    Costo total: ${{vuelo.precio}} dólares

    +
    Clase: {{vuelo.clase}}

    +
    Pasajeros: {{cantidadPasajeros}}

    +
  • +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    Vuelos reservados
    +
    +
  • +
    +
    {{vuelo.nombreAerolinea}}
    +
    {{vuelo.origen}} -> {{vuelo.destino}}
    +
    {{vuelo.fechaSalida}}
    +
    +
  • +
    + +
    +
    +
    +
    Informacion de los vuelos reservados
    +
    +
  • +
    +
    {{vuelo.origen}}, {{vuelo.destino}}-Ecuador

    +
    Salida: {{vuelo.fechaSalida}} a las {{vuelo.horaSalida}}:00h

    +
    Duración del viaje: {{vuelo.duracionVuelo}} minutos

    +
    Operado por: {{vuelo.nombreAerolinea}} minutos

    +
    Costo total: ${{vuelo.precio}} dólares

    +
    Clase: {{vuelo.clase}}

    +
    Pasajeros: {{cantidadPasajeros}}

    +
  • +
    - +
    -
    - + +
    +
    + +
    -
    -
    -
    Informacion de los vuelos reservados
    + +
    +
    +
    +
    Información del pago
    +
    +
    Nombre y apellido
    +
    + +
    +
    +
    +
    Cuenta:
    +
    + +
    +
    +
    +
    Banco:
    +
    + +
    +
    +
    + +
    +
    -
  • -
    Usted ha reservado un boleto en la aereolínea {{vuelo.nombreAerolinea}}

    -
    Con destino a {{vuelo.destino}} desde {{vuelo.origen}}

    -
    Para el día {{vuelo.fechaSalida}} a las {{vuelo.horaSalida}}:00h

    -
    Este viaje tiene una duración de {{vuelo.duracionVuelo}} minutos

    -
    Y el costo total de este boleto es de {{vuelo.precio}} dólares
    -
  • \ No newline at end of file diff --git a/src/app/components/escoger-ruta/escoger-ruta.component.ts b/src/app/components/escoger-ruta/escoger-ruta.component.ts index c347ab0..954956f 100644 --- a/src/app/components/escoger-ruta/escoger-ruta.component.ts +++ b/src/app/components/escoger-ruta/escoger-ruta.component.ts @@ -1,13 +1,33 @@ -import { Component, AfterViewInit, ElementRef, Renderer2, OnInit } from '@angular/core'; +import { + Component, + AfterViewInit, + ElementRef, + Renderer2, + OnInit, + Input, + ViewChild, + Output, + EventEmitter, +} from '@angular/core'; +import { NgForm } from '@angular/forms'; +import { Pago } from 'src/app/models/pago'; +import { Pasajero } from 'src/app/models/pasajero'; +import { Usuario } from 'src/app/models/usuario'; import { Vuelo } from 'src/app/models/vuelo'; import { Global } from 'src/app/services/global'; +import { PagoService } from 'src/app/services/pago.service'; +import { PasajeroService } from 'src/app/services/pasajero.service'; +import { UsuarioService } from 'src/app/services/usuario.service'; + import { VuelosService } from 'src/app/services/vuelo.service'; +import { render } from 'creditcardpayments/creditCardPayments'; +import { transition } from '@angular/animations'; @Component({ selector: 'app-escoger-ruta', templateUrl: './escoger-ruta.component.html', styleUrls: ['./escoger-ruta.component.css'], - providers: [VuelosService] + providers: [VuelosService, PasajeroService, UsuarioService, PagoService], }) export class EscogerRutaComponent implements AfterViewInit, OnInit { public vuelos: Vuelo[]; @@ -15,33 +35,183 @@ export class EscogerRutaComponent implements AfterViewInit, OnInit { public url: string; public precios: number[]; public aux: number; - constructor(private renderer: Renderer2, private el: ElementRef, private _vueloService: VuelosService + + //pasajero + public pasajero!: Pasajero; + public cantidadPasajeros: number; + public status: string; + + //usuario + public usuario!: Usuario; + public pago!: Pago; + public _idUsuario: string = ''; + + //valor Total + public valorTotal: number = 0; + + //ASIENTOS + rows: any[] = []; + disabledCheckboxes: string[] = []; + selected: number = 0; + isRandomActive = false; + public eventListenersAttached: boolean = false; + @Input() maxSelectedCheckboxes: number = 1; + @ViewChild('seatsContainer') seatsContainer!: ElementRef; + @Output() emitArrayAsientos = new EventEmitter(); + allCheckboxes: HTMLInputElement[] = []; + habilitadoImageSrc: string = '../assets/habilitado.jpg'; + desHabilitadoImageSrc: string = '../assets/desHabilitado.jpg'; + + constructor( + private renderer: Renderer2, + private el: ElementRef, + private _vueloService: VuelosService, + private _pasajeroService: PasajeroService, + private _pagoService: PagoService, + private _usuarioService: UsuarioService ) { this.url = Global.url; this.vuelos = []; this.vuelosReservados = []; this.precios = []; this.aux = 0; + this.cantidadPasajeros = 1; + this.status = ''; + this.pasajero = new Pasajero('', '', '', 0); + this.usuario = new Usuario('', '', 0, ''); + this.usuario = new Usuario('', '', 0, ''); + this.pago = new Pago(this.usuario.nombreApellido, 0, ''); + this.valorTotal = 0; + + //ASIENTOS + for (let rowNumber = 1; rowNumber <= 9; rowNumber++) { + const seats = ['A', 'B', 'C', 'D', 'E', 'F']; // Asientos de A a F por fila + const row = { + rowNumber: rowNumber, + seats: seats.map(seat => ({ + id: `${rowNumber}${seat}`, + selected: true, + enabled: true, // Inicialmente, todos los asientos están habilitados + imageSrc: this.habilitadoImageSrc // Inicialmente, se muestra la imagen habilitada + })) + }; + this.rows.push(row); + } + console.log(this.rows); } ngOnInit(): void { this.getVuelos(); } + + async getUser() { + + if(this._idUsuario === ''){ + console.log("No hay usuario"); + return; + } + const user = await fetch(`http://localhost:3600/obtener-usuario/${this._idUsuario}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', // Especifica el tipo de contenido JSON + }, + }); + + return user.json(); + } + + async paypalButton() { + + console.log(this.vuelosReservados); + + const response = await fetch('http://localhost:3600/create-order', { + method: 'POST', + body: JSON.stringify({ + value: this.valorTotal.toString() + }), + headers: { + 'Content-Type': 'application/json', // Especifica el tipo de contenido JSON + }, + }); + + const data = await response.json(); + const paypalWindow = window.open( + data.links[1].href, + 'PaypalPopup', + 'width=500,height=800' + ); + + if (paypalWindow) { + // Agrega esta comprobación para evitar el error + const checkWindowClosed = setInterval(() => { + if (paypalWindow.closed) { + clearInterval(checkWindowClosed); + // Después de que se cierre la ventana de PayPal, llama al endpoint /send-email + this.sendEmail(); + } + }, 1000); // Verifica cada segundo si la ventana de PayPal se ha cerrado + } else { + // Maneja la respuesta en caso de que no sea 200, por ejemplo, mostrando un mensaje de error. + console.error('Error en la solicitud:', response.status); + } + } + + async sendEmail() { + const dataUser = await this.getUser(); + + fetch('http://localhost:3600/send-email', { + method: 'POST', + body: JSON.stringify({ + name: dataUser.usuario.nombreApellido, + cedula: dataUser.usuario.cedula, + email: dataUser.usuario.correo, + value: this.valorTotal.toString(), + origen: this.vuelosReservados[0].origen, + destino: this.vuelosReservados[0].destino, + fechaSalida: this.vuelosReservados[0].fechaSalida, + horaSalida: this.vuelosReservados[0].horaSalida, + duracionVuelo: this.vuelosReservados[0].duracionVuelo, + nombreAerolinea: this.vuelosReservados[0].nombreAerolinea, + clase: this.vuelosReservados[0].clase, + }), + headers: { + 'Content-Type': 'application/json', // Especifica el tipo de contenido JSON + }, + // Puedes incluir un cuerpo si es necesario para enviar datos al endpoint /send-email + }) + .then((emailResponse) => { + if (emailResponse.status === 200) { + console.log('Email enviado con éxito'); + } else { + console.error('Error al enviar el email:', emailResponse.status); + } + }) + .catch((error) => { + console.error('Error al enviar el email:', error); + }); + } + + sumaValorTotal(){ + for (const vuelo of this.vuelosReservados) { + this.valorTotal = this.valorTotal + vuelo.precio; + } + } getVuelos() { this._vueloService.getVuelos().subscribe( - response => { + (response) => { if (response.vuelos) { this.vuelos = response.vuelos; } }, - error => { + (error) => { console.log(error); } - ) + ); } - + esRegreso: boolean = false; ngAfterViewInit(): void { - const checkboxes = this.el.nativeElement.querySelectorAll('.checkbox-input'); + const checkboxes = + this.el.nativeElement.querySelectorAll('.checkbox-input'); const fechaRegreso = this.el.nativeElement.querySelector('.fechaRegreso'); checkboxes.forEach((checkbox: HTMLElement) => { this.renderer.listen(checkbox, 'change', () => { @@ -53,6 +223,7 @@ export class EscogerRutaComponent implements AfterViewInit, OnInit { fechaRegreso.disabled = true; } else { fechaRegreso.disabled = false; + this.esRegreso = true; } } }); @@ -67,14 +238,24 @@ export class EscogerRutaComponent implements AfterViewInit, OnInit { inputElement.value = (currentValue - 1).toString(); for (let i = 0; i < this.vuelos.length; i++) { //no pagan = const menores2Value = parseInt((document.getElementById('menores2') as HTMLInputElement).value, 10); - const entre2y25Value = parseInt((document.getElementById('entre2y25') as HTMLInputElement).value, 10); - const entre25y65Value = parseInt((document.getElementById('entre25y65') as HTMLInputElement).value, 10); - const mayores65Value = parseInt((document.getElementById('mayores65') as HTMLInputElement).value, 10); + const entre2y25Value = parseInt( + (document.getElementById('entre2y25') as HTMLInputElement).value, + 10 + ); + const entre25y65Value = parseInt( + (document.getElementById('entre25y65') as HTMLInputElement).value, + 10 + ); + const mayores65Value = parseInt( + (document.getElementById('mayores65') as HTMLInputElement).value, + 10 + ); this.vuelos[i].precio = - (this.precios[i] * 0.8) * entre2y25Value + + this.precios[i] * 0.8 * entre2y25Value + entre25y65Value * this.precios[i] + - (this.precios[i] * 0.5) * mayores65Value; + this.precios[i] * 0.5 * mayores65Value; } + this.cantidadPasajeros--; } } @@ -87,14 +268,24 @@ export class EscogerRutaComponent implements AfterViewInit, OnInit { inputElement.value = (currentValue + 1).toString(); for (let i = 0; i < this.vuelos.length; i++) { //no pagan = const menores2Value = parseInt((document.getElementById('menores2') as HTMLInputElement).value, 10); - const entre2y25Value = parseInt((document.getElementById('entre2y25') as HTMLInputElement).value, 10); - const entre25y65Value = parseInt((document.getElementById('entre25y65') as HTMLInputElement).value, 10); - const mayores65Value = parseInt((document.getElementById('mayores65') as HTMLInputElement).value, 10); + const entre2y25Value = parseInt( + (document.getElementById('entre2y25') as HTMLInputElement).value, + 10 + ); + const entre25y65Value = parseInt( + (document.getElementById('entre25y65') as HTMLInputElement).value, + 10 + ); + const mayores65Value = parseInt( + (document.getElementById('mayores65') as HTMLInputElement).value, + 10 + ); this.vuelos[i].precio = - (this.precios[i] * 0.8) * entre2y25Value + + this.precios[i] * 0.8 * entre2y25Value + entre25y65Value * this.precios[i] + - (this.precios[i] * 0.5) * mayores65Value; + this.precios[i] * 0.5 * mayores65Value; } + this.cantidadPasajeros++; } } @@ -108,19 +299,96 @@ export class EscogerRutaComponent implements AfterViewInit, OnInit { mostrarSeccionVuelos: boolean = false; mostrarSeccionPasajeros: boolean = false; + noExistenVuelos: boolean = false; mostrarVuelos() { this.mostrarSeccionVuelos = true; this.mostrarSeccionPasajeros = true; for (const vuelo of this.vuelos) { this.precios.push(vuelo.precio); } - console.log(this.precios) + console.log(this.precios); + this._vueloService + .getVuelosConFiltros(this.origen, this.destino, this.fechaSalida) + .subscribe( + (response) => { + if (response.vuelos) { + this.vuelos = response.vuelos; + if (this.vuelos.length === 0) { + this.noExistenVuelos = true; + setTimeout(() => { + this.noExistenVuelos = false; + }, 6000); + } else { + this.mostrarSeccionVuelos = true; + this.mostrarSeccionPasajeros = true; + for (const vuelo of this.vuelos) { + this.precios.push(vuelo.precio); + } + console.log(this.precios); + } + } + }, + (error) => { + console.log(error); + this.noExistenVuelos = true; + setTimeout(() => { + this.noExistenVuelos = false; + }, 6000); + } + ); } - + public beneficiosTurista: boolean = false; + public beneficiosPrimera: boolean = false; selectFlight(i: number) { this.vuelosReservados[this.aux] = this.vuelos[i]; this.aux++; + this.beneficiosTurista = true; + if (this.esRegreso) { + this.esRegreso = false; + this._vueloService + .getVuelosConFiltros(this.destino, this.origen, this.fechaRegreso) + .subscribe( + (response) => { + if (response.vuelos) { + this.vuelos = response.vuelos; + if (this.vuelos.length === 0) { + this.noExistenVuelos = true; + setTimeout(() => { + this.noExistenVuelos = false; + }, 6000); + } else { + this.mostrarSeccionVuelos = true; + for (const vuelo of this.vuelos) { + this.precios.push(vuelo.precio); + } + console.log(this.precios); + } + } + }, + (error) => { + console.log(error); + } + ); + } else { + this.mostrarSeccionVuelos = false; + this.mostrarSeccionPasajeros = false; + this.mostrarPasajero(); + } + //this.beneficiosPrimera = true; + setTimeout(() => { + //this.beneficiosPrimera = false; + this.beneficiosTurista = false; + }, 20000); } + + public origen: string = ''; + public destino: string = ''; + public fechaSalida: string = ''; + public fechaRegreso: string = ''; + public clase: string = ''; + + buscarVuelosConFiltros() {} + mostrarContenido: boolean = true; mostrarSeccionCarrito: boolean = false; abrirCarrito() { @@ -128,7 +396,7 @@ export class EscogerRutaComponent implements AfterViewInit, OnInit { this.mostrarSeccionCarrito = true; this.mostrarBotonResumen = true; } - exit(){ + exit() { this.mostrarContenido = true; this.mostrarSeccionCarrito = false; this.mostrarSeccionVuelos = false; @@ -136,9 +404,265 @@ export class EscogerRutaComponent implements AfterViewInit, OnInit { this.mostrarSeccionResumen = false; this.mostrarBotonResumen = false; } + mostrarSeccionResumen: boolean = false; + mostrarSeccionResumenP: boolean = false; mostrarBotonResumen: boolean = false; + mostrarResumen() { this.mostrarSeccionResumen = true; } + mostrarResumenP() { + this.mostrarSeccionResumenP = true; + // this.mostrarInformacionUsuario = false; + this.sumaValorTotal(); + console.log(this.vuelosReservados); + } + + //pasajeros + mostrarInformacionPasajero: boolean = false; + i: number = 1; + mostrarPasajero() { + this.mostrarInformacionPasajero = true; + } + + guardarPasajero(form: NgForm) { + this._pasajeroService.guardarPasajero(this.pasajero).subscribe( + (response) => { + if (response.pasajero) { + this.status = 'success'; + form.reset(); + console.log(this.pasajero); + } else { + this.status = 'failed'; + } + }, + (error) => { + console.log(error); + } + ); + } + getRange(num: number): number[] { + return new Array(num); + } + //pago + aux1: number = 1; + mostrarPago() { + if (this.aux1 === this.cantidadPasajeros) { + this.mostrarInformacionPasajero = false; + this.mostrarInformacionUsuario = true; + this.mostrarBotonesPago = true; + } + this.aux1++; + } + /*contacto*/ + mostrarInformacionUsuario: boolean = false; + mostrarBotonesPago: boolean = false; + + guardarUsuario(form: NgForm) { + this._usuarioService.guardarUsuario(this.usuario).subscribe( + (response) => { + if (response.usuario) { + this._idUsuario = response.usuario._id; //Guardamos el id del usuario + this.status = 'success'; + form.reset(); + } else { + this.status = 'failed'; + } + }, + (error) => { + console.log(error); + } + ); + } + + mostrarBotonPago: boolean = false; + mostrarBotones() { + //this.mostrarBotonPago = true; + this.mostrarInformacionUsuario = false; + } + + guardarPago(form: NgForm) { + this._pagoService.guardarPago(this.pago).subscribe( + (response) => { + if (response.pago) { + this.status = 'success'; + console.log(response.pago._id); + form.reset(); + console.log(this.pago); + } else { + this.status = 'failed'; + } + }, + (error) => { + console.log(error); + } + ); + } + + mostrarPagoT: boolean = false; + mostrarPagoTotal() { + this.mostrarPagoT = true; + } + + + //ASIENTOS + + toggleSeatState(seat: any) { + seat.enabled = !seat.enabled; + seat.imageSrc = seat.enabled ? this.habilitadoImageSrc : this.desHabilitadoImageSrc; + this.rows.forEach((row) => { + row.seats.forEach((seat: any) => { + seat.selected = false; + }); + }); + } + + toggleRandom(): void { + this.isRandomActive = !this.isRandomActive; + + if (!this.isRandomActive) { + this.selectedCheckboxes = []; + this.selected = 0; + const checkboxes = document.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach((checkbox) => { + if (checkbox instanceof HTMLInputElement) { + checkbox.checked = false; + } + }); + this.rows.forEach((row) => { + row.seats.forEach((seat: any) => { + seat.selected = false; + }); + }); + this.emitArrayAsientos.emit(this.selectedCheckboxes); + console.log(this.selectedCheckboxes); + for (const row of this.rows) { + for (const seat of row.seats) { + if (seat.selected) { + seat.enabled = !seat.enabled; + seat.imageSrc = seat.enabled ? this.habilitadoImageSrc : this.desHabilitadoImageSrc; + } + } + } + } + + if (this.isRandomActive) { + // Aquí puedes llamar a la función para seleccionar aleatoriamente los checkboxes + this.selectedCheckboxes = []; + this.selected = 0; + const checkboxes = document.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach((checkbox) => { + if (checkbox instanceof HTMLInputElement) { + checkbox.checked = false; + } + }); + this.rows.forEach((row) => { + row.seats.forEach((seat: any) => { + seat.selected = false; + }); + }); + this.selectRandomCheckboxes(); + this.emitArrayAsientos.emit(this.selectedCheckboxes); + console.log(this.selectedCheckboxes); + for (const row of this.rows) { + for (const seat of row.seats) { + if (seat.selected) { + seat.enabled = !seat.enabled; + seat.imageSrc = seat.enabled ? this.habilitadoImageSrc : this.desHabilitadoImageSrc; + } + } + } + } + } + + selectedCheckboxes: string[] = []; + + extractSelectedCheckboxes() { + this.selectedCheckboxes = []; + for (const row of this.rows) { + for (const seat of row.seats) { + if (seat.selected) { + this.selectedCheckboxes.push(seat.id); + } + } + } + } + + handleCheckboxChange(event: Event) { + const checkbox = event.target as HTMLInputElement; + const rowIdMatches = checkbox.id.match(/^(\d+)/); + if (rowIdMatches) { + const rowId = Number(rowIdMatches[1]) - 1; + if (rowId >= 0 && rowId <= this.rows.length) { + const row = this.rows[rowId].seats; + const selectedSeat = row.find((seat: any) => seat.id === checkbox.id); + + if (selectedSeat) { + selectedSeat.selected = checkbox.checked; + this.extractSelectedCheckboxes(); + } + + if (checkbox.checked) { + if (this.selectedCheckboxes.length <= this.maxSelectedCheckboxes) { + this.selected++; + if (this.selectedCheckboxes.length === this.maxSelectedCheckboxes) { + } + } else { + checkbox.checked = false; + } + } else { + this.selected--; + } + } else { + console.log('Row ID fuera de límites:', rowId); + } + } + } + + disableCheckboxes() { + const checkboxes: NodeListOf = this.el.nativeElement.querySelectorAll( + 'input[type="checkbox"]' + ); + + checkboxes.forEach((checkbox) => { + if (this.disabledCheckboxes.includes(checkbox.id)) { + checkbox.disabled = true; + } else { + checkbox.disabled = false; + } + }); + } + +// Selecciona aleatoriamente 'count' checkboxes de la matriz de checkboxes disponibles +selectRandomCheckboxes() { + const checkboxes = Array.from(document.querySelectorAll('input[type="checkbox"]:not(:disabled)')) as HTMLInputElement[]; + const availableCheckboxes = checkboxes.filter((checkbox) => !this.selectedCheckboxes.includes(checkbox.id)); + let count = this.maxSelectedCheckboxes - this.selectedCheckboxes.length; + + while (count > 0 && availableCheckboxes.length > 0) { + const randomIndex = Math.floor(Math.random() * availableCheckboxes.length); + const randomCheckbox = availableCheckboxes.splice(randomIndex, 1)[0]; + randomCheckbox.checked = true; + + // Simular un evento 'change' en el checkbox seleccionado aleatoriamente + const event = new Event('change', { bubbles: true }); + Object.defineProperty(event, 'target', { value: randomCheckbox, enumerable: true }); + + this.handleCheckboxChange(event); + + count--; + } +} + + displayStyle = "none"; + + openPopup() { + this.displayStyle = "block"; + } + closePopup() { + this.emitArrayAsientos.emit(this.selectedCheckboxes); + this.displayStyle = "none"; + } + } diff --git a/src/app/components/home/home.component.css b/src/app/components/home/home.component.css new file mode 100644 index 0000000..b015a54 --- /dev/null +++ b/src/app/components/home/home.component.css @@ -0,0 +1,181 @@ +* { + box-sizing: border-box; + font-family: 'Segoe UI'; +} + +header { + position: absolute; + background-color: transparent; +} + +header a{ + color: white; +} + +header a:hover { + width: auto; + height: 40px; + padding-left: 20px; + padding-right: 20px; + background: #F23B88; + justify-content: center; + align-items: center; + display: flex; + transform: scale(1.1); + transition: transform 0.3s ease; +} + +.showcase { + position: absolute; + right: 0; + width: 100%; + min-height: 100vh; + padding: 100px; + display: flex; + justify-content: space-between; + align-items: center; + background: #111; + transition: 0.5s; + z-index: 2; +} + +.showcase.active { + right: 300px; +} + +.showcase video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0.9; +} + +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #156275; + mix-blend-mode: overlay; +} + +.text { + position: relative; + z-index: 10; +} + +.text h2 { + font-size: 3em; + font-weight: 800; + color: #fff; + line-height: 1em; + text-transform: uppercase; +} + +.text h3 { + font-size: 5em; + font-weight: 700; + color: #fff; + line-height: 1em; + text-transform: uppercase; +} + +.text p { + font-size: 1.1em; + color: #fff; + margin: 20px 0; + font-weight: 400; + max-width: 700px; +} + +.text a { + display: inline-block; + font-size: 1em; + background: #fff; + padding: 10px 30px; + text-transform: uppercase; + text-decoration: none; + font-weight: 500; + margin-top: 10px; + color: #111; + letter-spacing: 2px; + transition: 0.2s; +} + +.text a:hover { + letter-spacing: 6px; +} + +.social { + position: absolute; + z-index: 10; + bottom: 20px; + display: flex; + justify-content: center; + align-items: center; +} + +.social li { + list-style: none; +} + +.social li a { + display: inline-block; + margin-right: 20px; + filter: invert(1); + transform: scale(0.5); + transition: 0.5s; +} + +.social li a:hover { + transform: scale(0.5) translateY(-15px); +} + +.menu { + position: absolute; + top: 0; + right: 0; + width: 300px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.menu ul { + position: relative; +} + +.menu ul li { + list-style: none; +} + +.menu ul li a { + text-decoration: none; + font-size: 24px; + color: #111; +} + +.menu ul li a:hover { + color: #03a9f4; +} + +@media (max-width: 991px) { + + .showcase, + .showcase header { + padding: 40px; + } + + .text h2 { + font-size: 3em; + } + + .text h3 { + font-size: 2em; + } +} \ No newline at end of file diff --git a/src/app/components/home/home.component.html b/src/app/components/home/home.component.html new file mode 100644 index 0000000..437b39d --- /dev/null +++ b/src/app/components/home/home.component.html @@ -0,0 +1,34 @@ + +
    + + + Inicio + Buscar vuelos + Promociones + Login + + +
    +
    + +
    +
    +

    AGENCIA

    +

    DANDO ALAS

    +

    Somos una agencia de viajes que brinda los mejores servicios + turísticos superando las expectativas de nuestros pasajeros + dando a conocer las riquezas de nuestro Ecuador.

    + ¿A DÓNDE QUIERES IR? +
    + +
    + \ No newline at end of file diff --git a/src/app/components/home/home.component.spec.ts b/src/app/components/home/home.component.spec.ts new file mode 100644 index 0000000..ba1b4a3 --- /dev/null +++ b/src/app/components/home/home.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [HomeComponent] + }); + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/home/home.component.ts b/src/app/components/home/home.component.ts new file mode 100644 index 0000000..0cb0d0f --- /dev/null +++ b/src/app/components/home/home.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.css'] +}) +export class HomeComponent { + +} diff --git a/src/app/components/login/login.component.css b/src/app/components/login/login.component.css new file mode 100644 index 0000000..225ee5c --- /dev/null +++ b/src/app/components/login/login.component.css @@ -0,0 +1,105 @@ +@import url(https://fonts.googleapis.com/css?family=Roboto:300); + +* { + box-sizing: border-box; + font-family: 'Segoe UI'; +} + +.login-page { + width: 360px; + padding: 8% 0 0; + margin: auto; +} + +.form { + position: relative; + z-index: 1; + background: #FFFFFF; + max-width: 360px; + margin: 100px auto 100px; + padding: 45px; + text-align: center; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); +} + +.form input { + font-family: "Roboto", sans-serif; + outline: 0; + background: #f2f2f2; + width: 100%; + border: 0; + margin: 0 0 15px; + padding: 15px; + box-sizing: border-box; + font-size: 14px; +} + +.boton { + width: 100%; + padding: 15px; +} + +.form .message { + margin: 15px 0 0; + color: #b3b3b3; + font-size: 12px; +} + +.form .message a { + color: #4CAF50; + text-decoration: none; +} + +.form .register-form { + display: none; +} + +.container { + position: relative; + z-index: 1; + max-width: 300px; + margin: 0 auto; +} + +.container:before, +.container:after { + content: ""; + display: block; + clear: both; +} + +.container .info { + margin: 50px auto; + text-align: center; +} + +.container .info h1 { + margin: 0 0 15px; + padding: 0; + font-size: 36px; + font-weight: 300; + color: #1a1a1a; +} + +.container .info span { + color: #4d4d4d; + font-size: 12px; +} + +.container .info span a { + color: #000000; + text-decoration: none; +} + +.container .info span .fa { + color: #EF3B3A; +} + +body { + /* background: #76b852; */ + /* background: rgb(141,194,111); */ + /* background: linear-gradient(90deg, rgba(141,194,111,1) 0%, rgba(118,184,82,1) 50%); */ + font-family: "Roboto", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/src/app/components/login/login.component.html b/src/app/components/login/login.component.html new file mode 100644 index 0000000..6f7be5b --- /dev/null +++ b/src/app/components/login/login.component.html @@ -0,0 +1,68 @@ + +
    + + + Inicio + Buscar vuelo + Promociones +
    + Login +
    +
    + +
    + + diff --git a/src/app/components/login/login.component.spec.ts b/src/app/components/login/login.component.spec.ts new file mode 100644 index 0000000..360f9f2 --- /dev/null +++ b/src/app/components/login/login.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [LoginComponent] + }); + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts new file mode 100644 index 0000000..a35f4df --- /dev/null +++ b/src/app/components/login/login.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { VuelosService } from 'src/app/services/vuelo.service'; +import { Route, Router } from '@angular/router'; +import { AuthService } from 'src/app/services/auth.service'; +import { UserI } from 'src/app/models/user'; +import { OnInit } from '@angular/core'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.css'], + providers: [VuelosService], +}) +export class LoginComponent implements OnInit { + constructor(private authService: AuthService, private router: Router) {} + + ngOnInit(): void {} + + onLogin(form: any): void { + this.authService.login(form.value).subscribe( res => { + this.router.navigateByUrl('guardar-vuelo'); + }); + } +} diff --git a/src/app/components/promociones/promociones.component.css b/src/app/components/promociones/promociones.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/promociones/promociones.component.html b/src/app/components/promociones/promociones.component.html new file mode 100644 index 0000000..00de872 --- /dev/null +++ b/src/app/components/promociones/promociones.component.html @@ -0,0 +1,101 @@ + +
    + + + Inicio + + Buscar vuelos + + + Login + +
    + +
    +
    + +
    + +
    +

    Nuestras recomendaciones

    +
    + +
    + Moda +

    Cuenca es una ciudad de las montañas andinas del sur de Ecuador. Es conocida + por la ribera del río Tomebamba y las artesanías, incluidos los sombreros Panamá. Su plaza + central, el Parque Calderón, alberga la Catedral Nueva con su domo azul y la Catedral Vieja del + siglo XVI, que actualmente es un museo religioso. El Museo y Parque Arqueológico Pumapungo + exhibe ruinas y artefactos de la ciudad inca de Tomebamba. ― Google + Tiempo: 9°C, viento del N a 5 km/h, humedad del 75 %

    +
    +
    + Moda +

    Guayaquil es una ciudad portuaria de Ecuador, conocida como la puerta de acceso a las playas del + Pacífico y a las Islas Galápagos. A lo largo del río Guayas se extiende el malecón Simón + Bolívar, donde está el monumento La Rotonda. En el norte, el barrio Las Peñas está lleno de + casas coloridas. Las escaleras bordeadas de cafés y galerías de arte llegan hasta el cerro Santa + Ana que incluye la capilla de Santa Ana, un faro y vistas de la ciudad. ― Google + Tiempo: 23°C, viento del SO a 14 km/h, humedad del 83 %

    +
    + +
    + Moda +

    Otavalo es una ciudad en el altiplano andino de la provincia de Imbabura, al norte de Ecuador. + Está rodeada de volcanes, como el volcán Imbabura. Es conocida por su mercado de Otavalo en la + central Plaza de Ponchos, donde los lugareños vestidos con ropa indígena tradicional venden + textiles coloridos y artesanías. Muy cerca se encuentra el Museo Tejidos El Obraje, que muestra + exposiciones textiles y demostraciones de tejido. + Población: 39,354 (2010) + Elevación: 2,532 m + Tiempo: 6°C, viento del E a 6 km/h, humedad del 78 %

    +
    + +
    + Moda +

    El cantón Lago Agrio cuenta con un aeropuerto remodelado recientemente de una longitud de pista + de 2.303 m; y un ancho de 45 m. También cuenta con un taxi way (corredor de recorrido) de 1.500 + m por un ancho de 30 m, siendo esta pista la segunda más extensa de la Amazonía. En este + aeropuerto operan las compañías Tame y VIP con frecuencias diarias. Posee una terminal terrestre + con destinos operados por las siguientes compañías: Baños, Esmeraldas, Jumandi, San Cristóbal, + Valle del Chota, Carlos Aray, Zaracay, Occidental, Putumayo, Loja y Petrolera Shushufindi.En + Nueva Loja se perforó el primer pozo petrolero en la Amazonía (Lago Agrio-1). Es por este motivo + que se la conoce como la capital petrolera de Ecuador. La ciudad se ha transformado + considerablemente en estas últimas 4 décadas. + En Nueva Loja se perforó el primer pozo petrolero en la Amazonía (Lago Agrio-1). Es por este + motivo que se la conoce como la capital petrolera de Ecuador. La ciudad se ha transformado + considerablemente en estas últimas 4 décadas.

    +
    +
    + Moda +

    La Troncal es una localidad situada al centro sur de Ecuador. Geográficamente se localiza en la + llanura de los Andes, con una altura de 140 m s. n. m. y una temperatura privilegiada que oscila + entre los 18 °C y 24 °C. Cuenta con importantes recursos naturales, como ríos, cascadas, + montañas y las conocidas aguas termales, muy concurridas por los turistas que llegan en busca de + relax y entretenimiento. Su principal actividad económica es la agricultura, especialmente de + caña de azúcar; así como el comercio, la artesanía y el turismo ecológico en menor escala

    +
    + +
    + Moda +

    Machala, capital de la Provincia de El Oro, es un cantón agrícola productivo y con un gran + movimiento comercial, constituyéndose en el polo económico del sur ecuatoriano. Sus pobladores + se dedican a la actividad bananera, por ello es reconocida internacionalmente como “Capital + Bananera del mundo”. La siembra y cosecha de camarón es otra de las actividades productivas. Su + población se dedica en su mayoría a la actividad agrícola, industrial y portuaria, por ello es + reconocida internacionalmente como “Capital Bananera del mundo”. La ciudad es el centro + político, financiero y económico de la provincia, y uno de los principales del país, alberga + grandes organismos culturales, financieros, administrativos y comerciales. Es conocida como la + Capital Mundial del Banano, porque desde allí a través del Puerto Bolívar se exporta esta + preciada fruta a todo el mundo.

    +
    + +
    +
    +
    + \ No newline at end of file diff --git a/src/app/components/promociones/promociones.component.spec.ts b/src/app/components/promociones/promociones.component.spec.ts new file mode 100644 index 0000000..0c524cb --- /dev/null +++ b/src/app/components/promociones/promociones.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PromocionesComponent } from './promociones.component'; + +describe('PromocionesComponent', () => { + let component: PromocionesComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PromocionesComponent] + }); + fixture = TestBed.createComponent(PromocionesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/promociones/promociones.component.ts b/src/app/components/promociones/promociones.component.ts new file mode 100644 index 0000000..8812397 --- /dev/null +++ b/src/app/components/promociones/promociones.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-promociones', + templateUrl: './promociones.component.html', + styleUrls: ['./promociones.component.css'] +}) +export class PromocionesComponent { + +} diff --git a/src/app/models/jwt-response.ts b/src/app/models/jwt-response.ts new file mode 100644 index 0000000..49603db --- /dev/null +++ b/src/app/models/jwt-response.ts @@ -0,0 +1,9 @@ +export interface JwtResponseI { + dataUser: { + id: number, + name: string, + email: string, + accessToken: string, + expiresIn: string + } + } \ No newline at end of file diff --git a/src/app/models/pago.ts b/src/app/models/pago.ts new file mode 100644 index 0000000..cf7766a --- /dev/null +++ b/src/app/models/pago.ts @@ -0,0 +1,7 @@ +export class Pago { + constructor( + public propietario: string, + public cuenta: number, + public banco: string + ) { } +} diff --git a/src/app/models/pasajero.ts b/src/app/models/pasajero.ts new file mode 100644 index 0000000..9961158 --- /dev/null +++ b/src/app/models/pasajero.ts @@ -0,0 +1,8 @@ +export class Pasajero { + constructor( + public nombres: string, + public apellidos: string, + public fechaNacimiento: string, + public cedula: number, + ) { } +} diff --git a/src/app/models/user.ts b/src/app/models/user.ts new file mode 100644 index 0000000..6a97528 --- /dev/null +++ b/src/app/models/user.ts @@ -0,0 +1,6 @@ +export interface UserI { + id: number, + name: string, + email: string, + password: string + } \ No newline at end of file diff --git a/src/app/models/usuario.ts b/src/app/models/usuario.ts new file mode 100644 index 0000000..302f214 --- /dev/null +++ b/src/app/models/usuario.ts @@ -0,0 +1,8 @@ +export class Usuario { + constructor( + public nombreApellido: string, + public correo: string, + public cedula: number, + public telefono: string, + ) { } +} diff --git a/src/app/models/vuelo.ts b/src/app/models/vuelo.ts index d834387..43b956c 100644 --- a/src/app/models/vuelo.ts +++ b/src/app/models/vuelo.ts @@ -1,3 +1,5 @@ +import { Pasajero } from "./pasajero"; + export class Vuelo { constructor( public _id: string, @@ -11,15 +13,9 @@ export class Vuelo { public duracionVuelo: string, public pasajeros: Pasajero[], public costoMaletaAdicional: number, + public clase: string, + public numAsientos: number, public estado: string, public disponibilidad: boolean ) { } } - -export class Pasajero { - constructor( - public identificacion: string, - public numeroAsiento: number, - public costo: number - ) { } -} diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..9a7967f --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from "@angular/common/http"; +import { UserI } from "../models/user"; +import { JwtResponseI } from "../models/jwt-response"; +import { tap } from "rxjs/operators"; +import { Observable, BehaviorSubject } from "rxjs"; + +@Injectable() +export class AuthService { + AUTH_SERVER: string = 'http://localhost:3600'; + authSubject = new BehaviorSubject(false); + private token: string; + constructor(private httpClient: HttpClient) { + this.token = ''; + } + + register(user: UserI): Observable { + return this.httpClient.post(`${this.AUTH_SERVER}/register`, + user).pipe(tap( + (res: JwtResponseI) => { + if (res) { + // guardar token + this.saveToken(res.dataUser.accessToken, res.dataUser.expiresIn); + } + }) + ); + } + + login(user: UserI): Observable { + return this.httpClient.post(`${this.AUTH_SERVER}/login`, + user).pipe(tap( + (res: JwtResponseI) => { + if (res) { + // guardar token + this.saveToken(res.dataUser.accessToken, res.dataUser.expiresIn); + } + }) + ); + } + + logout(): void { + this.token = ''; + localStorage.removeItem("ACCESS_TOKEN"); + localStorage.removeItem("EXPIRES_IN"); + } + + private saveToken(token: string, expiresIn: string): void { + localStorage.setItem("ACCESS_TOKEN", token); + localStorage.setItem("EXPIRES_IN", expiresIn); + this.token = token; + } + + private getToken(): string { + if (!this.token) { + this.token = localStorage.getItem("ACCESS_TOKEN")!; + } + return this.token || ''; // Si this.token es null, devuelve una cadena vacía en su lugar + } + + +} \ No newline at end of file diff --git a/src/app/services/pago.service.ts b/src/app/services/pago.service.ts new file mode 100644 index 0000000..d4e8c2a --- /dev/null +++ b/src/app/services/pago.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders} from "@angular/common/http"; +import { Observable } from "rxjs"; +import { Global } from "./global"; +import { Pago } from "../models/pago"; + +@Injectable() +export class PagoService{ + public url:string; + constructor( + private _http:HttpClient + ){ + this.url=Global.url; + } + guardarPago(pago:Pago):Observable{ + let params=JSON.stringify(pago); + let headers=new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.post(this.url+'guardar-pago', params, {headers:headers}); + } +} + diff --git a/src/app/services/pasajero.service.ts b/src/app/services/pasajero.service.ts new file mode 100644 index 0000000..9680735 --- /dev/null +++ b/src/app/services/pasajero.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders} from "@angular/common/http"; +import { Pasajero } from "../models/pasajero"; +import { Observable } from "rxjs"; +import { Global } from "./global"; + +@Injectable() +export class PasajeroService{ + public url:string; + constructor( + private _http:HttpClient + ){ + this.url=Global.url; + } + getPasajeros():Observable{ + let headers=new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.get(this.url+'obtener-pasajeros',{headers:headers}); + } + guardarPasajero(pasajero:Pasajero):Observable{ + let params=JSON.stringify(pasajero); + let headers=new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.post(this.url+'guardar-pasajero', params, {headers:headers}); + } + getPasajero(id:String):Observable{ + let headers=new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.get(this.url+'obtener-pasajero'+id, {headers:headers}); + } +} \ No newline at end of file diff --git a/src/app/services/usuario.service.ts b/src/app/services/usuario.service.ts new file mode 100644 index 0000000..063dd37 --- /dev/null +++ b/src/app/services/usuario.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders} from "@angular/common/http"; +import { Observable } from "rxjs"; +import { Global } from "./global"; +import { Usuario } from "../models/usuario"; + +@Injectable() +export class UsuarioService{ + public url:string; + constructor( + private _http:HttpClient + ){ + this.url=Global.url; + } + getUsuarios():Observable{ + let headers=new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.get(this.url+'obtener-usuarios',{headers:headers}); + } + guardarUsuario(usuario:Usuario):Observable{ + let params=JSON.stringify(usuario); + let headers=new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.post(this.url+'guardar-usuario', params, {headers:headers}); + } + getUsuario(id:String):Observable{ + let headers=new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.get(this.url+'obtener-usuario'+id, {headers:headers}); + } +} + diff --git a/src/app/services/vuelo.service.ts b/src/app/services/vuelo.service.ts index 9d8e67d..df36731 100644 --- a/src/app/services/vuelo.service.ts +++ b/src/app/services/vuelo.service.ts @@ -1,33 +1,99 @@ -import { Injectable } from "@angular/core"; -import { HttpClient, HttpHeaders} from "@angular/common/http"; -import { Vuelo } from "../models/vuelo"; -import { Observable } from "rxjs"; -import { Global } from "./global"; +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; +import { Vuelo } from '../models/vuelo'; +import { Observable } from 'rxjs'; +import { Global } from './global'; + +interface ParamsType { + origen?: string; + destino?: string; + fechaSalida?: string; +} @Injectable() -export class VuelosService{ - public url:string; - constructor( - private _http:HttpClient - ){ - this.url=Global.url; - } - getVuelos():Observable{ - let headers=new HttpHeaders().set('Content-Type', 'application/json'); - return this._http.get(this.url+'obtener-vuelos',{headers:headers}); - } - guardarVuelo(vuelo:Vuelo):Observable{ - let params=JSON.stringify(vuelo); - let headers=new HttpHeaders().set('Content-Type', 'application/json'); - return this._http.post(this.url+'guardar-vuelo', params, {headers:headers}); +export class VuelosService { + public url: string; + constructor(private _http: HttpClient) { + this.url = Global.url; + } + getVuelos(): Observable { + let headers = new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.get(this.url + 'obtener-vuelos', { headers: headers }); + } + guardarVuelo(vuelo: Vuelo): Observable { + let params = JSON.stringify(vuelo); + let headers = new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.post(this.url + 'guardar-vuelo', params, { + headers: headers, + }); + } + getVuelo(id: String): Observable { + let headers = new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.get(this.url + 'obtener-vuelo' + id, { + headers: headers, + }); + } + getUltimoNumeroVuelo(): Observable { + let headers = new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.get(this.url + 'ultimo-numero-vuelo', { + headers: headers, + }); + } + + eliminarVuelo(id: string): Observable { + let headers = new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.delete(this.url + 'eliminar-vuelo/' + id, { + headers: headers, + }); + } + + actualizarVuelo(vuelo: Vuelo) { + const headers = new HttpHeaders().set('Content-Type', 'application/json'); + return this._http.put(`${this.url}actualizar-vuelo/${vuelo._id}`, vuelo); + } + + getVuelosConFiltros( + origen: string, + destino: string, + fechaSalida: string + ): Observable { + let headers = new HttpHeaders().set('Content-Type', 'application/json'); + let params = new HttpParams(); + if (origen) { + params = params.set('origen', origen); } - getVuelo(id:String):Observable{ - let headers=new HttpHeaders().set('Content-Type', 'application/json'); - return this._http.get(this.url+'obtener-vuelo'+id, {headers:headers}); + if (destino) { + params = params.set('destino', destino); } - getUltimoNumeroVuelo(): Observable { - let headers = new HttpHeaders().set('Content-Type', 'application/json'); - return this._http.get(this.url + 'ultimo-numero-vuelo', {headers: headers}); + +// kevinC7 + if (fechaSalida) { + params = params.set('fechaSalida', fechaSalida); + +// eliminarVuelo(id: string): Observable { +// let headers = new HttpHeaders().set('Content-Type', 'application/json'); +// return this._http.delete(this.url + 'eliminar-vuelo/' + id, { headers: headers }); +// } + + +// getVuelosConFiltros(origen: string, destino: string, fechaSalida: string): Observable { +// let headers = new HttpHeaders().set('Content-Type', 'application/json'); +// let params = new HttpParams(); +// if (origen) { +// params = params.set('origen', origen); +// } +// if (destino) { +// params = params.set('destino', destino); +// } +// if (fechaSalida) { +// params = params.set('fechaSalida', fechaSalida); +// } +// return this._http.get(this.url + 'buscar', { headers: headers, params: params }); +// // develop } + return this._http.get(this.url + 'buscar', { + headers: headers, + params: params, + }); + } } - diff --git a/src/assets/Cuenca.jpg b/src/assets/Cuenca.jpg new file mode 100644 index 0000000..5cdaa18 Binary files /dev/null and b/src/assets/Cuenca.jpg differ diff --git a/src/assets/Guayaquil.jpg b/src/assets/Guayaquil.jpg new file mode 100644 index 0000000..11b080b Binary files /dev/null and b/src/assets/Guayaquil.jpg differ diff --git a/src/assets/LagoAgrio.jpg b/src/assets/LagoAgrio.jpg new file mode 100644 index 0000000..7f14f00 Binary files /dev/null and b/src/assets/LagoAgrio.jpg differ diff --git a/src/assets/Machala.jpg b/src/assets/Machala.jpg new file mode 100644 index 0000000..bd98b60 Binary files /dev/null and b/src/assets/Machala.jpg differ diff --git a/src/assets/Otavalo.jpg b/src/assets/Otavalo.jpg new file mode 100644 index 0000000..33f16f6 Binary files /dev/null and b/src/assets/Otavalo.jpg differ diff --git a/src/assets/Quito.jpg b/src/assets/Quito.jpg new file mode 100644 index 0000000..38d9011 Binary files /dev/null and b/src/assets/Quito.jpg differ diff --git a/src/assets/check.jpg b/src/assets/check.jpg new file mode 100644 index 0000000..461abbc Binary files /dev/null and b/src/assets/check.jpg differ diff --git a/src/assets/checkX.jpg b/src/assets/checkX.jpg new file mode 100644 index 0000000..aa49c45 Binary files /dev/null and b/src/assets/checkX.jpg differ diff --git a/src/assets/desHabilitado.jpg b/src/assets/desHabilitado.jpg new file mode 100644 index 0000000..bcdbecf Binary files /dev/null and b/src/assets/desHabilitado.jpg differ diff --git a/src/assets/destino.png b/src/assets/destino.png new file mode 100644 index 0000000..0ac317f Binary files /dev/null and b/src/assets/destino.png differ diff --git a/src/assets/fondo.jpg b/src/assets/fondo.jpg index c68a44d..4488fb4 100644 Binary files a/src/assets/fondo.jpg and b/src/assets/fondo.jpg differ diff --git a/src/assets/habilitado.jpg b/src/assets/habilitado.jpg new file mode 100644 index 0000000..bfd7658 Binary files /dev/null and b/src/assets/habilitado.jpg differ diff --git a/src/assets/troncal.jpg b/src/assets/troncal.jpg new file mode 100644 index 0000000..c221ce4 Binary files /dev/null and b/src/assets/troncal.jpg differ diff --git a/src/assets/video.mp4 b/src/assets/video.mp4 new file mode 100644 index 0000000..0dc0d9f Binary files /dev/null and b/src/assets/video.mp4 differ diff --git a/src/assets/video4.mp4 b/src/assets/video4.mp4 new file mode 100644 index 0000000..2a2d4e3 Binary files /dev/null and b/src/assets/video4.mp4 differ diff --git a/src/index.html b/src/index.html index 9e1db4c..aeddf3b 100644 --- a/src/index.html +++ b/src/index.html @@ -6,6 +6,7 @@ + diff --git a/src/styles.css b/src/styles.css index 7f481da..f7fef23 100644 --- a/src/styles.css +++ b/src/styles.css @@ -3,6 +3,8 @@ * { margin: 0px; padding: 0px; + box-sizing: border-box; + font-family: 'Segoe UI'; } body { @@ -45,11 +47,16 @@ header a { text-align: center; color: black; font-size: 20px; - font-family: Poppins; font-weight: 300; word-wrap: break-word } +header a:hover { + transform: scale(1.2); + transition: transform 0.3s ease; + color: #F23B88; +} + .logo { width: 221px; border-radius: 11px; @@ -81,7 +88,7 @@ header a { outline: none; } -.search input[type="text"]:focus{ +.search input[type="text"]:focus { color: #555; border: none; } @@ -95,23 +102,138 @@ header a { display: none; } +.formulario { + height: auto; + padding-bottom: 50px; + padding-top: 150px; + flex-direction: column; + justify-content: center; + align-items: center; + display: flex; +} + +.titulo { + color: #000000; + font-size: 25px; + font-family: Nunito Sans; + text-shadow: 3px 3px 5px rgba(126, 138, 162, 0.5); + padding-bottom: 50px; +} + +.detalles { + width: auto; + height: auto; + flex-direction: column; + justify-content: center; + display: flex; + align-items: center; + gap: 10px; + position: relative; + z-index: 1; + padding: 30px; + text-align: center; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); +} + +.seccion { + flex-direction: row; + justify-content: center; + display: flex; + align-items: center; + gap: 10px; + padding-bottom: 20px; +} + +.informacion { + width: 220px; + font-family: Nunito Sans; + color: black; + align-content: center; + font-size: 18px; + text-align: left; +} + +.atributo { + align-self: stretch; + height: 40px; + padding: 0px 15px; + width: 260px; + background: white; + font-family: "Roboto", sans-serif; + border: 0.50px #8CB4F1 solid; + justify-content: space-between; + align-items: center; + gap: 10px; + display: inline-flex; +} + +.texto, +.atributo input[type="number"], +.atributo input[type="text"], +.atributo input[type="email"], +.nombreAerolinea, +.origen, +.destino, +.horaSalida, +.clase { + width: 220px; + color: #555; + border: none; + font-size: 15px; + background: transparent; + font-family: "Roboto", sans-serif; + word-wrap: break-word; + outline: none; +} + +.fechaSalida, +.fechaNacimiento { + width: 200px; + border: none; + color: #555; + font-size: 16px; + outline: none; +} + +.textoFecha { + width: 220px; + color: #555; + border: none; + font-size: 18px; + font-family: Poppins; + background: transparent; + word-wrap: break-word; + outline: none; +} + .boton { width: 400px; height: 48px; margin: 0 auto; background: #F23B88; + color: white; justify-content: center; - border-radius: 4px; - box-shadow: 0px 5px 5px rgba(0, 0, 0, 0.50); - border-radius: 15px; + box-shadow: 0px 7px 7px rgba(0, 0, 0, 0.50); + border-radius: 7px; + border: 0; overflow: hidden; align-items: center; display: flex; - font-size: 20px; - font-family: Nunito Sans; + font-size: 15px; font-weight: 400; cursor: pointer; text-decoration: none; + font-family: "Roboto", sans-serif; + text-transform: uppercase; + outline: 0; + -webkit-transition: all 0.3 ease; + transition: all 0.3 ease; + cursor: pointer; +} + +.boton:hover, +.boton:active { + background: #dd7495; } .disabled-button { @@ -120,6 +242,10 @@ header a { cursor: not-allowed; } +.disabled-button:hover { + background-color: #bdc3c7; +} + .boton a { text-align: center; color: white; @@ -127,4 +253,196 @@ header a { font-family: Poppins; font-weight: 600; word-wrap: break-word -} \ No newline at end of file +} +/*alerta*/ +.alert { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: 30px; + width: 490px; + font-size: 20px; + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + animation: fadeOut 6s ease-in-out; + z-index: 100; +} + +#banner{ + width: 98%; + height: 450px; + border: 1px solid white; + box-shadow: 0px 0px 20px gray; + background-image: url("./assets/destino.png"); + background-size: cover; + background-position: -200px -200px; + float: left; + overflow: hidden; + animation: backBanner 10s linear; + margin-left: 1%; /* Agregar margen izquierdo */ + margin-right: 1%; +} +@keyframes backBanner { + 0%{ + background-position: 0px 0px; + } + 100%{ + background-position: -200px -200px; + } +} + + +@keyframes fadeOut { + 0% { + opacity: 1; + } + + 25% { + opacity: 0.66; + } + 50% { + opacity: 0.33; + } + + 100% { + opacity: 0; + } +} + +#cards h1{ + width: 100%; + float: left; + text-transform: uppercase; + font-family: 'finger'; + font-weight: normal; + text-align: center; + color: #c57d98; + font-size: 35px; + margin-top: 20px; + -webkit-animation: focus-in-contract-bck 1s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; + animation: focus-in-contract-bck 1s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; +} +@-webkit-keyframes focus-in-contract-bck{ + 0%{ + letter-spacing: 1em; + -webkit-transform: translateZ(300px); + transform: translateZ(300px); + -webkit-filter: blur(12px); + filter: blur(12px); + opacity: 0; + } + 100%{ + -webkit-transform: translateZ(12px); + transform: translateZ(12px); + -webkit-filter: blur(0); + filter: blur(0); + opacity: 1; + } +} +@keyframes focus-in-contract-bck{ + 0%{ + letter-spacing: 1em; + -webkit-transform: translateZ(300px); + transform: translateZ(300px); + -webkit-filter: blur(12px); + filter: blur(12px); + opacity: 0; + } + 100%{ + -webkit-transform: translateZ(12px); + transform: translateZ(12px); + -webkit-filter: blur(0); + filter: blur(0); + opacity: 1; + } +} +#cards h1::before{ + content: ""; + height: 3px; + width: 100px; + background-color: #c57d98; + display: inline-block; + margin-top: 0px; + margin-right: 30px; + vertical-align: middle; +} +#cards h1::after{ + content: ""; + height: 3px; + width: 50px; + background-color: #c57d98; + display: inline-block; + margin-top: 0px; + margin-left: 30px; + vertical-align: middle; +} + + +.card { + float: left; + width: 40%; + height: auto; + border: transparent; + outline: none; + border-radius: 20px; + box-shadow: 1px 1px 2px #ddd; + background-color:white; + overflow: hidden; + margin: 50px; + margin-top: 20px; + cursor: pointer; + position: relative; + z-index: 1; + + transition: all 300ms; + justify-content: center; + align-items: center; +} +.card img{ + width: 100%; + position: relative; + z-index: 2; + margin: 10px; + transform: rotate(0deg) scale(1); +} +.card img:hover{ + transform: rotate(0deg) scale(1.2); +} +.card .category{ + width: 80%; + margin-top: -30px; + height: 50px; + padding-top: 10px; + padding-left: 20px; + font-family: "TrebuchetMS"; + font-size: 15px; + font-weight: bold; + text-align: center; + color: black; + background-color: white; + position: relative; + z-index: 3; + border-radius: 20px; + transform: all 300ms; +} +.card:hover .category{ + color: #00ceae; + animation: showCategory 400ms linear; +} +@keyframes showCategory { + from{ + transform: translateY(-300%); + color: black; + } + to{ + transform: translateY(0%); + color: white; + } +}