vbs registration
continuous-integration/drone Build is failing
Details
continuous-integration/drone Build is failing
Details
parent
4739e0d6e1
commit
a8abe3f3d8
|
|
@ -22,6 +22,7 @@ import { ImageComponent } from './components/image/image.component';
|
|||
import { VideoComponent } from './components/video/video.component';
|
||||
import { FCCPermitPageComponent } from './components/fcc-permit-page/fcc-permit-page.component';
|
||||
import { BalloonComponent } from './components/balloon/balloon.component';
|
||||
import { VbsPageComponent } from './components/vbs-page/vbs-page.component';
|
||||
|
||||
const routes =
|
||||
[
|
||||
|
|
@ -120,6 +121,10 @@ const routes =
|
|||
{
|
||||
path: 'balloon',
|
||||
component: BalloonComponent
|
||||
},
|
||||
{
|
||||
path: 'vbs',
|
||||
component: VbsPageComponent
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ import { MatButtonModule,
|
|||
MatOptionModule,
|
||||
MatRadioModule,
|
||||
MatAutocompleteModule,
|
||||
MatCheckboxModule} from '@angular/material';
|
||||
MatCheckboxModule,
|
||||
MatDatepickerModule,
|
||||
MatNativeDateModule,
|
||||
MatCardModule} from '@angular/material';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import 'hammerjs';
|
||||
|
||||
|
|
@ -86,7 +89,7 @@ import { VideoComponent } from './components/video/video.component';
|
|||
import { YoutubeListService } from './components/youtube-list/youtube-list-service';
|
||||
import { VideoItemComponent } from './components/video-item/video-item.component';
|
||||
import { YoutubePopupComponent } from './components/popups/youtube-popup/youtube-popup.component';
|
||||
|
||||
import { VbsPageComponent } from './components/vbs-page/vbs-page.component';
|
||||
|
||||
|
||||
|
||||
|
|
@ -143,7 +146,8 @@ import { YoutubePopupComponent } from './components/popups/youtube-popup/youtube
|
|||
YoutubeListComponent,
|
||||
VideoComponent,
|
||||
VideoItemComponent,
|
||||
YoutubePopupComponent
|
||||
YoutubePopupComponent,
|
||||
VbsPageComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
|
@ -162,7 +166,10 @@ import { YoutubePopupComponent } from './components/popups/youtube-popup/youtube
|
|||
MatDialogModule,
|
||||
MatAutocompleteModule,
|
||||
MatRadioModule,
|
||||
MatCheckboxModule
|
||||
MatCheckboxModule,
|
||||
MatCardModule,
|
||||
MatDatepickerModule,
|
||||
MatNativeDateModule
|
||||
],
|
||||
providers: [LoginService,PrintService,UserService,GoogleAnalyticsService,SermonService,TransactionService,EventService,EmailService,MissionarySupportService,YoutubeListService,WindowRefService],
|
||||
entryComponents: [AddSermonPopupComponent,
|
||||
|
|
|
|||
|
|
@ -41,8 +41,17 @@
|
|||
<div id="content-wrapper">
|
||||
|
||||
<div style="text-align: center; position: relative; margin-bottom: 15px; " *ngIf="showVBS">
|
||||
<div style="position: absolute; width: 100%; height: calc(65% + 15px); top: 35%; background-color: white;"></div>
|
||||
<img src="assets/images/home-images/tiny/VBS_small.jpg" style="width:75%; max-width: 350px; position: relative; z-index: 1; box-shadow: 0 0 20px pink;" >
|
||||
<div style="position: absolute; width: 100%; height: calc(65% + 15px); top: 35%; background-color: rgb(240,240,240);"></div>
|
||||
<a routerLink="/vbs" style="display: block; width:75%; max-width: 350px; position: relative; z-index: 1; margin-left: auto; margin-right: auto;">
|
||||
<img src="assets/images/home-images/tiny/VBS_small.jpg" style="width:100%; height: auto; display: block; box-shadow: 0 0 20px gray;" >
|
||||
<div style="margin-top: 30px; font-size: 30px;">
|
||||
Click To Register!
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div style="height: 2px; background-color: lightgray;">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row tint" *ngIf="showVGC" >
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export class HomeComponent {
|
|||
backgroundTop: string = "0px";
|
||||
|
||||
public get showVGC() : boolean {
|
||||
|
||||
return false;
|
||||
let maxDate = new Date(2024,7,15,11); // August 15th 2018 -- Set the month one month behind since JavaScript dates are 0 based
|
||||
let now = new Date();
|
||||
if (now.getFullYear() > maxDate.getFullYear()) return false;
|
||||
|
|
@ -63,7 +63,8 @@ export class HomeComponent {
|
|||
return true;
|
||||
}
|
||||
public get showVBS(): boolean {
|
||||
let maxDate = new Date(2019,7,22); // August 22nd 2018 -- Set the month one month behind since JavaScript dates are 0 based
|
||||
return false;
|
||||
let maxDate = new Date(2026,7,13); // August 22nd 2018 -- Set the month one month behind since JavaScript dates are 0 based
|
||||
let now = new Date();
|
||||
if (now.getFullYear() > maxDate.getFullYear()) return false;
|
||||
if (now.getFullYear() == maxDate.getFullYear()) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
padding: 20px 10px 10px 10px;
|
||||
}
|
||||
|
||||
.width100{
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.side-bar{
|
||||
width: 30%;
|
||||
display: table-cell;
|
||||
|
|
@ -27,6 +31,10 @@
|
|||
vertical-align: top;
|
||||
}
|
||||
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media(max-width:800px){
|
||||
.side-bar{
|
||||
display: block;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<div id="content-wrapper">
|
||||
<div id="content">
|
||||
<div class="main-content align-top">
|
||||
<div class="main-content align-top" [class.width100]="hideAlways">
|
||||
<ng-content select="[mainContent]"></ng-content>
|
||||
</div><!--
|
||||
--><div class="side-bar align-top" [class.hide-on-mobile]="hideSideBarOnMobile" [class.fixed-side-bar]="fixedSideBar">
|
||||
--><div class="side-bar align-top" [class.hide-on-mobile]="hideSideBarOnMobile" [class.display-none]="true" [class.fixed-side-bar]="fixedSideBar">
|
||||
<ng-content select="[sideBar]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||
})
|
||||
export class SecondaryPageComponent implements OnInit {
|
||||
@Input()
|
||||
public hideSideBarOnMobile: boolean;
|
||||
public hideSideBarOnMobile: boolean = true;
|
||||
@Input()
|
||||
public fixedSideBar: boolean;
|
||||
public fixedSideBar: boolean = true;
|
||||
@Input()
|
||||
public hideAlways: boolean = true;
|
||||
|
||||
constructor(){
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
.w-50 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section-header{
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid gray;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-row-1-col {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.form-row-2-col {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media(max-width:600px){
|
||||
.form-row-2-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media(max-width:800px){
|
||||
.form-container {
|
||||
width: auto;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
mat-label {
|
||||
font-weight: bold;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1.15rem;
|
||||
margin-top: 35px;
|
||||
color: black;
|
||||
font-weight: 400;
|
||||
font-family: Arial, Helvetica, sans-serif
|
||||
}
|
||||
|
||||
ol > li {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
mat-label {
|
||||
font-size: 1.15rem;
|
||||
color: black;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
label.sub {
|
||||
margin-top: 0px;
|
||||
font-size: .85rem;
|
||||
}
|
||||
|
||||
mat-radio-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
mat-radio-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
|
||||
<secondary-page-component [hideAlways]="true" >
|
||||
<div mainContent>
|
||||
<div class="form-container">
|
||||
<h1 class="center" style="background-color: lightblue; padding:10px;">
|
||||
Vacation Bible School Registration
|
||||
</h1>
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
||||
<div *ngIf="studentStep">
|
||||
<h2 class="center" style="margin:25px;">Student Information</h2>
|
||||
<div class="form-row-2-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('firstName')">
|
||||
<mat-label>First Name</mat-label>
|
||||
<input matInput type="text" formControlName="firstName" required>
|
||||
<mat-error>Please enter student first name</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('lastName')">
|
||||
<mat-label>Last Name</mat-label>
|
||||
<input matInput type="text" formControlName="lastName" required>
|
||||
<mat-error>Please enter student last name</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-2-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('grade')">
|
||||
<mat-label>Grade</mat-label>
|
||||
<input matInput type="number" formControlName="grade" required>
|
||||
<mat-error>Please enter student grade</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('dob')">
|
||||
<mat-label>Birthday</mat-label>
|
||||
<input matInput [matDatepicker]="picker" (click)="picker.open()" formControlName="dob" required readonly>
|
||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker touchUi #picker></mat-datepicker>
|
||||
<mat-error>Please enter student birthday</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-1-col">
|
||||
<div>
|
||||
<label id="studentHasAllergiesLabel">Does the student have any allergies?</label>
|
||||
<mat-radio-group aria-label="Student has allergies" formControlName="hasAllergies" aria-labelledby="studentHasAllergiesLabel">
|
||||
<mat-radio-button class="radio-button" value="true">Yes</mat-radio-button>
|
||||
<mat-radio-button class="radio-button" value="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<mat-form-field *ngIf="form.get('hasAllergies').value === 'true'" class="w-100" [class.background-error]="hasErrors('allergies')">
|
||||
<mat-label>Allergies</mat-label>
|
||||
<textarea matInput type="text" formControlName="allergies"></textarea>
|
||||
<mat-error>Please enter student allergies</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-1-col">
|
||||
<div>
|
||||
<label id="studentHasDietaryRestrictionsLabel">Does the student have any dietary restrictions?</label>
|
||||
<mat-radio-group aria-label="Student has allergies" formControlName="hasDietaryRestrictions" aria-labelledby="studentHasDietaryRestrictionsLabel">
|
||||
<mat-radio-button class="radio-button" value="true">Yes</mat-radio-button>
|
||||
<mat-radio-button class="radio-button" value="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<mat-form-field *ngIf="form.get('hasDietaryRestrictions').value === 'true'" class="w-100" [class.background-error]="hasErrors('dietaryRestrictions')">
|
||||
<mat-label>Dietary Restrictions</mat-label>
|
||||
<textarea matInput type="text" formControlName="dietaryRestrictions"></textarea>
|
||||
<mat-error>Please enter student dietary restrictions</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-1-col">
|
||||
<div>
|
||||
<label id="studentAttendedInPastLabel">Has the student attended VBS in the past?</label>
|
||||
<mat-radio-group aria-label="Student Attended In Past" formControlName="attendedInPast" aria-labelledby="studentAttendedInPastLabel">
|
||||
<mat-radio-button class="radio-button" value="true">Yes</mat-radio-button>
|
||||
<mat-radio-button class="radio-button" value="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row-1-col">
|
||||
<div>
|
||||
<label id="studentRidesBus">Does the student currently ride the bus or van?</label>
|
||||
<mat-radio-group aria-label="Student Rides The Bus or Van" formControlName="ridesBus" aria-labelledby="studentRidesBus">
|
||||
<mat-radio-button class="radio-button" value="true">Yes</mat-radio-button>
|
||||
<mat-radio-button class="radio-button" value="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: flex-end; margin-bottom:20px;">
|
||||
<button [disabled]="!studentComplete()" mat-raised-button color="primary" (click)="gotoParentStep()">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="parentStep">
|
||||
<h2 class="center" style="margin:25px;">Parent/Guardian Information</h2>
|
||||
<div class="form-row-2-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('parentFirstName')">
|
||||
<mat-label>First Name</mat-label>
|
||||
<input matInput type="text" formControlName="parentFirstName" required>
|
||||
<mat-error>Please enter parent/guardian name</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('parentLastName')">
|
||||
<mat-label>Last Name</mat-label>
|
||||
<input matInput type="text" formControlName="parentLastName" required>
|
||||
<mat-error>Please enter parent/guardian name</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-1-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('address1')">
|
||||
<mat-label>Address Line 1</mat-label>
|
||||
<input matInput type="text" formControlName="address1" required>
|
||||
<mat-error>Please enter parent/guardian address</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('address2')">
|
||||
<mat-label>Address Line 2</mat-label>
|
||||
<input matInput type="text" formControlName="address2">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-2-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('city')">
|
||||
<mat-label>City</mat-label>
|
||||
<input matInput type="text" formControlName="city" required>
|
||||
<mat-error>Please enter parent/guardian city</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('state')">
|
||||
<mat-label>State *</mat-label>
|
||||
<mat-select formControlName="state">
|
||||
<!-- Empty option to allow clearing the choice -->
|
||||
<mat-option [value]="null">-- Select a State --</mat-option>
|
||||
|
||||
<!-- Dynamically loop over the array mapping code to value and name to label -->
|
||||
<mat-option *ngFor="let state of stateList" [value]="state.code">
|
||||
{{ state.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>Please enter parent/guardian state</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('zip')">
|
||||
<mat-label>Zip</mat-label>
|
||||
<input matInput type="text" formControlName="zip" required>
|
||||
<mat-error>Please enter parent/guardian zip</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-2-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('parentHomePhone')">
|
||||
<mat-label>Home Phone</mat-label>
|
||||
<input matInput type="tel" formControlName="parentHomePhone">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('parentMobilePhone')">
|
||||
<mat-label>Mobile Phone</mat-label>
|
||||
<input matInput type="tel" formControlName="parentMobilePhone" required>
|
||||
<mat-error>Please enter parent/guardian mobile phone (must be 10 digits)</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="form-row-2-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('parentWorkPhone')">
|
||||
<mat-label>Work Phone</mat-label>
|
||||
<input matInput type="tel" formControlName="parentWorkPhone">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('parentEmail')">
|
||||
<mat-label>Email</mat-label>
|
||||
<input matInput type="email" formControlName="parentEmail" required>
|
||||
<mat-error>Please enter parent/guardian email</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 20px;">
|
||||
<button mat-raised-button color="primary" (click)="gotoStudentStep()">Back</button>
|
||||
<button [disabled]="!parentComplete()" mat-raised-button color="primary" (click)="gotoEmergencyStep()">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="emergencyStep">
|
||||
<h2 class="center" style="margin:25px;">Emergency Contact Information</h2>
|
||||
<div class="form-row-1-col">
|
||||
<mat-checkbox matPrefix formControlName="emergencySameAsParent">Same as Parent/Guardian</mat-checkbox>
|
||||
</div>
|
||||
<div class="form-row-2-col">
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('emergencyFirstName')">
|
||||
<mat-label>First Name</mat-label>
|
||||
<input matInput type="text" formControlName="emergencyFirstName">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('emergencyLastName')">
|
||||
<mat-label>Last Name</mat-label>
|
||||
<input matInput type="text" formControlName="emergencyLastName">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="w-100" [class.background-error]="hasErrors('emergencyPhone')">
|
||||
<mat-label>Phone</mat-label>
|
||||
<input matInput type="tel" formControlName="emergencyPhone">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<input type="hidden" formControlName="hp">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 20px;">
|
||||
<button [disabled]="submitting" mat-raised-button color="primary" (click)="gotoParentStep()">Back</button>
|
||||
<button [disabled]="!emergencyComplete() || submitting" mat-raised-button color="primary" (click)="submit()">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="completeStep">
|
||||
<h2 class="center" style="margin:25px;">Complete!</h2>
|
||||
<h4 class="center">{{form.get('firstName').value}} has been registered!</h4>
|
||||
<div class="center" style="margin-top:20px;">
|
||||
<button mat-raised-button color="primary" (click)="registerAnotherStudent()">Register Another Student</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</secondary-page-component>
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators, FormArray, FormControl, ValidatorFn, ValidationErrors } from '@angular/forms';
|
||||
import { MissionarySupportService } from 'src/app/services/missionary-support-service';
|
||||
import { MatDialog, MatDialogConfig } from '@angular/material';
|
||||
import { OkPopupComponent } from '../popups/ok-popup/ok-popup.component';
|
||||
import { EmailService } from 'src/app/services/email.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-vbs-page',
|
||||
templateUrl: './vbs-page.component.html',
|
||||
styleUrls: ['./vbs-page.component.css']
|
||||
})
|
||||
export class VbsPageComponent implements OnInit {
|
||||
|
||||
form: FormGroup;
|
||||
submitButtonText: string = 'Submit';
|
||||
submitButtonDisabled: boolean = false;
|
||||
errorMessages: string[] = [];
|
||||
public students: Student[] = [];
|
||||
public studentStep: boolean = true;
|
||||
public parentStep: boolean = false;
|
||||
public emergencyStep: boolean = false;
|
||||
public completeStep: boolean = false;
|
||||
public submitting: boolean = false;
|
||||
|
||||
constructor(private formBuilder: FormBuilder, private matDialog: MatDialog, private emailService: EmailService, private missionarySupportService: MissionarySupportService) {
|
||||
//this.setupForm();
|
||||
this.form = this.formBuilder.group({
|
||||
firstName: ['', [Validators.required]],
|
||||
lastName: ['', [Validators.required]],
|
||||
grade: ['', [Validators.required]],
|
||||
dob: [null, [Validators.required]],
|
||||
hasAllergies: [null, [Validators.required]],
|
||||
allergies: ['', [Validators.required]],
|
||||
hasDietaryRestrictions: [null, [Validators.required]],
|
||||
dietaryRestrictions: ['', [Validators.required]],
|
||||
attendedInPast: [null, [Validators.required]],
|
||||
ridesBus: [null, [Validators.required]],
|
||||
parentFirstName: ['', [Validators.required]],
|
||||
parentLastName: ['', [Validators.required]],
|
||||
parentHomePhone: ['', []],
|
||||
parentMobilePhone: ['', [Validators.required]],
|
||||
parentWorkPhone: ['', []],
|
||||
parentEmail: ['', [Validators.required]],
|
||||
address1: ['', [Validators.required]],
|
||||
address2: ['', [Validators.required]],
|
||||
city: ['', [Validators.required]],
|
||||
state: ['', [Validators.required]],
|
||||
zip: ['', [Validators.required]],
|
||||
emergencySameAsParent: ['', []],
|
||||
emergencyFirstName: ['', []],
|
||||
emergencyLastName: ['', []],
|
||||
emergencyPhone: ['', []],
|
||||
hp: ['.', [Validators.required]]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
}
|
||||
|
||||
hasErrors(formControlName: string): boolean {
|
||||
let res = this.form.get(formControlName) && typeof this.form.get(formControlName).errors !== 'undefined';
|
||||
if (res === true) {
|
||||
res = this.form.get(formControlName).dirty || (this.form.get(formControlName).touched && this.form.get(formControlName).valid === false);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
studentComplete(): boolean {
|
||||
var firstName = this.form.get('firstName');
|
||||
var lastName = this.form.get('lastName');
|
||||
var grade = this.form.get('grade');
|
||||
var dob = this.form.get('dob');
|
||||
var hasAllergies = this.form.get('hasAllergies');
|
||||
var hasDietaryRestrictions = this.form.get('hasDietaryRestrictions');
|
||||
var allergies = this.form.get('allergies');
|
||||
var dietaryRestrictions = this.form.get('dietaryRestrictions');
|
||||
var attendedInPast = this.form.get('attendedInPast');
|
||||
var ridesBus = this.form.get('ridesBus');
|
||||
|
||||
var hasFirst = firstName && firstName.value && firstName.value.length > 2;
|
||||
var hasLast = lastName && lastName.value && lastName.value.length > 2;
|
||||
var hasGrade = grade && grade.value && grade.value > 0;
|
||||
var hasDob = dob && dob.value != null;
|
||||
var hasAllergiesComplete = hasAllergies && hasAllergies.value != null;
|
||||
var hasDietaryRestrictionsComplete = hasDietaryRestrictions && hasDietaryRestrictions.value != null;
|
||||
var allergiesComplete = allergies && allergies.value && allergies.value.length > 2;
|
||||
var dietaryRestrictionsComplete = dietaryRestrictions && dietaryRestrictions.value && dietaryRestrictions.value.length > 2;
|
||||
var hasAttendedInPast = attendedInPast && attendedInPast.value != null;
|
||||
var hasRidesBus = ridesBus && ridesBus.value != null;
|
||||
|
||||
var validAllergies = hasAllergiesComplete && ((hasAllergies.value === 'true' && allergiesComplete) || (hasAllergies.value === 'false'));
|
||||
var validDietaryRestrictions = hasDietaryRestrictionsComplete && ((hasDietaryRestrictions.value === 'true' && dietaryRestrictionsComplete) || (hasDietaryRestrictions.value === 'false'));
|
||||
|
||||
return hasFirst && hasLast && hasGrade && hasDob && validAllergies && validDietaryRestrictions && hasRidesBus && hasAttendedInPast;
|
||||
}
|
||||
|
||||
parentComplete(): boolean {
|
||||
var first = this.form.get('parentFirstName');
|
||||
var last = this.form.get('parentLastName');
|
||||
var mobilePhone = this.form.get('parentMobilePhone');
|
||||
var email = this.form.get('parentEmail');
|
||||
var address1 = this.form.get('address1');
|
||||
var city = this.form.get('city');
|
||||
var state = this.form.get('state');
|
||||
var zip = this.form.get('zip');
|
||||
|
||||
var hasFirst = first && first.value && first.value.length > 1;
|
||||
var hasLast = last && last.value && last.value.length > 1;
|
||||
var hasMobilePhone = mobilePhone && mobilePhone.value && mobilePhone.value.length > 9;
|
||||
var hasEmail = email && email.value && this.isValidEmail(email.value);
|
||||
var hasAddress1 = address1 && address1.value && address1.value.length > 2;
|
||||
var hasCity = city && city.value && city.value.length > 2;
|
||||
var hasState = state && state.value && state.value.length > 0;
|
||||
var hasZip = zip && zip.value && zip.value.length > 2;
|
||||
|
||||
return hasFirst && hasLast && hasMobilePhone && hasEmail && hasAddress1 && hasCity && hasState && hasZip;
|
||||
}
|
||||
|
||||
emergencyComplete(): boolean {
|
||||
var same = this.form.get('emergencySameAsParent');
|
||||
var first = this.form.get('emergencyFirstName');
|
||||
var last = this.form.get('emergencyLastName');
|
||||
var phone = this.form.get('emergencyPhone');
|
||||
|
||||
var isSame = same && same.value && same.value === true;
|
||||
var hasFirst = first && first.value && first.value.length > 2;
|
||||
var hasLast = last && last.value && last.value.length > 2;
|
||||
var hasPhone = phone && phone.value && phone.value.length > 9;
|
||||
|
||||
if (isSame) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasFirst && hasLast && hasPhone;
|
||||
}
|
||||
|
||||
isValidEmail(email: string) {
|
||||
// The official regex standard used by most frontend validation engines
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
gotoStudentStep() {
|
||||
this.studentStep = true;
|
||||
this.parentStep = false;
|
||||
this.emergencyStep = false;
|
||||
this.completeStep = false;
|
||||
}
|
||||
|
||||
gotoParentStep() {
|
||||
this.studentStep = false;
|
||||
this.parentStep = true;
|
||||
this.emergencyStep = false;
|
||||
this.completeStep = false;
|
||||
}
|
||||
|
||||
gotoEmergencyStep() {
|
||||
this.studentStep = false;
|
||||
this.parentStep = false;
|
||||
this.emergencyStep = true;
|
||||
this.completeStep = false;
|
||||
}
|
||||
|
||||
gotoCompleteStep() {
|
||||
this.studentStep = false;
|
||||
this.parentStep = false;
|
||||
this.emergencyStep = false;
|
||||
this.completeStep = true;
|
||||
this.submitting = false;
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.submitting === true) {
|
||||
return;
|
||||
}
|
||||
this.submitting = true;
|
||||
this.emailService.sendVbsEmail(this.form.value)
|
||||
.subscribe(
|
||||
success => {this.gotoCompleteStep()},
|
||||
error => {this.submitError();});
|
||||
}
|
||||
|
||||
private submitError(){
|
||||
this.submitting = false;
|
||||
console.error("error");
|
||||
let opts = new MatDialogConfig;
|
||||
opts.data = { title:'Error','message':'Error submitting registration form. Please try again or contact us at (406) 494 - 5028.' };
|
||||
let popup = this.matDialog.open(OkPopupComponent,opts);
|
||||
this.submitButtonText = "Submit";
|
||||
this.submitButtonDisabled = false;
|
||||
}
|
||||
|
||||
registerAnotherStudent() {
|
||||
this.completeStep = false;
|
||||
this.studentStep = true;
|
||||
this.form.get('firstName').reset();
|
||||
this.form.get('lastName').reset();
|
||||
this.form.get('grade').reset();
|
||||
this.form.get('dob').reset();
|
||||
this.form.get('hasAllergies').reset();
|
||||
this.form.get('allergies').reset();
|
||||
this.form.get('hasDietaryRestrictions').reset();
|
||||
this.form.get('dietaryRestrictions').reset();
|
||||
this.form.get('attendedInPast').reset();
|
||||
this.form.get('ridesBus').reset();
|
||||
}
|
||||
|
||||
public stateList = [
|
||||
{ code: 'AL', name: 'Alabama' }, { code: 'AK', name: 'Alaska' }, { code: 'AZ', name: 'Arizona' },
|
||||
{ code: 'AR', name: 'Arkansas' }, { code: 'CA', name: 'California' }, { code: 'CO', name: 'Colorado' },
|
||||
{ code: 'CT', name: 'Connecticut' }, { code: 'DE', name: 'Delaware' }, { code: 'FL', name: 'Florida' },
|
||||
{ code: 'GA', name: 'Georgia' }, { code: 'HI', name: 'Hawaii' }, { code: 'ID', name: 'Idaho' },
|
||||
{ code: 'IL', name: 'Illinois' }, { code: 'IN', name: 'Indiana' }, { code: 'IA', name: 'Iowa' },
|
||||
{ code: 'KS', name: 'Kansas' }, { code: 'KY', name: 'Kentucky' }, { code: 'LA', name: 'Louisiana' },
|
||||
{ code: 'ME', name: 'Maine' }, { code: 'MD', name: 'Maryland' }, { code: 'MA', name: 'Massachusetts' },
|
||||
{ code: 'MI', name: 'Michigan' }, { code: 'MN', name: 'Minnesota' }, { code: 'MS', name: 'Mississippi' },
|
||||
{ code: 'MO', name: 'Missouri' }, { code: 'MT', name: 'Montana' }, { code: 'NE', name: 'Nebraska' },
|
||||
{ code: 'NV', name: 'Nevada' }, { code: 'NH', name: 'New Hampshire' }, { code: 'NJ', name: 'New Jersey' },
|
||||
{ code: 'NM', name: 'New Mexico' }, { code: 'NY', name: 'New York' }, { code: 'NC', name: 'North Carolina' },
|
||||
{ code: 'ND', name: 'North Dakota' }, { code: 'OH', name: 'Ohio' }, { code: 'OK', name: 'Oklahoma' },
|
||||
{ code: 'OR', name: 'Oregon' }, { code: 'PA', name: 'Pennsylvania' }, { code: 'RI', name: 'Rhode Island' },
|
||||
{ code: 'SC', name: 'South Carolina' }, { code: 'SD', name: 'South Dakota' }, { code: 'TN', name: 'Tennessee' },
|
||||
{ code: 'TX', name: 'Texas' }, { code: 'UT', name: 'Utah' }, { code: 'VT', name: 'Vermont' },
|
||||
{ code: 'VA', name: 'Virginia' }, { code: 'WA', name: 'Washington' }, { code: 'WV', name: 'West Virginia' },
|
||||
{ code: 'WI', name: 'Wisconsin' }, { code: 'WY', name: 'Wyoming' }
|
||||
];
|
||||
}
|
||||
|
||||
class Student {
|
||||
public firstName: string;
|
||||
public lastName: string;
|
||||
public grade: number;
|
||||
public birthday: string;
|
||||
public allergies: string;
|
||||
public dietaryRestrictions: string;
|
||||
public attendedInPast: boolean;
|
||||
public ridesBus: boolean;
|
||||
public guardian: Guardian
|
||||
public emergencyContact: EmergencyContact;
|
||||
}
|
||||
|
||||
class Guardian {
|
||||
public firstName: string;
|
||||
public lastName: string;
|
||||
}
|
||||
|
||||
class EmergencyContact {
|
||||
public firstName: string;
|
||||
public lastName: string;
|
||||
}
|
||||
|
|
@ -20,4 +20,5 @@ export const USER_GET_ALL_URL = environment.baseUrl + "/api2/users/a";
|
|||
export const LOGIN_URL = environment.baseUrl + '/api2/login';
|
||||
export const LOGIN_VALIDATE_TOKEN = '';
|
||||
export const EMAIL_URL = environment.baseUrl + "/api2/email";
|
||||
export const VBS_REGISTRATION_URL = environment.baseUrl + "/api2/vbs";
|
||||
export const RANDOM_VERSE_URL = "//www.kingjamesbibleonline.org/popular-bible-verses-widget.php";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { catchError } from 'rxjs/operators';
|
|||
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { EMAIL_URL } from '../constants/urls';
|
||||
import { EMAIL_URL, VBS_REGISTRATION_URL } from '../constants/urls';
|
||||
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -32,6 +32,11 @@ export class EmailService {
|
|||
|
||||
}
|
||||
|
||||
sendVbsEmail(formData) {
|
||||
return this.httpClient.post(VBS_REGISTRATION_URL, formData, {withCredentials:true})
|
||||
.pipe(catchError(this.handleError));
|
||||
}
|
||||
|
||||
private handleError(error: HttpErrorResponse) {
|
||||
if (error.error instanceof ErrorEvent) {
|
||||
// A client-side or network error occurred. Handle it accordingly.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ router.use("/events", require("./events"));
|
|||
router.use("/login", require("./login"));
|
||||
router.use("/email", require("./email"));
|
||||
router.use("/transactions", require("./transactions"));
|
||||
router.use("/vbs", require("./vbs"));
|
||||
|
||||
|
||||
router.use('/share',require('./share'));
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,181 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
let transporter = nodemailer.createTransport({
|
||||
host: 'smtp.ionos.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth:{
|
||||
user: 'mail@ofbbutte.com',
|
||||
pass: '@2014OfbPwd'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
router.get("/",function(req,res){
|
||||
res.status(200).json({"message":"Hello World"});
|
||||
return;
|
||||
});
|
||||
|
||||
router.post("/", function(req,res){
|
||||
console.log(req.body);
|
||||
//This is the honeypot field
|
||||
//If it has something in it then we know it was filled out by a bot
|
||||
if (!req.body.hp || req.body.hp != '.'){
|
||||
res.status(200).json({"status":200,"message":"Success!"});
|
||||
return;
|
||||
}
|
||||
|
||||
let mailOptions = {
|
||||
from: 'donotreply@ofbbutte.com',
|
||||
to: 'djabsher@gmail.com',
|
||||
subject: 'OFB - VBS Registration' + req.body.firstName,
|
||||
html: generateRegistrationEmailHTML(req.body)
|
||||
};
|
||||
transporter.sendMail(mailOptions,(error, info) =>{
|
||||
if (error){
|
||||
console.log(error);
|
||||
res.status(400).json({"status":400,"message":"There was an error","error":error.response});
|
||||
} else {
|
||||
res.status(200).json({"status":200,"message":"Success"});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Generates a sectioned HTML email string for the registration form.
|
||||
* Displays all fields even if they are empty.
|
||||
* @param {Object} data - The value object from this.form.value
|
||||
* @returns {string} Ready-to-send HTML string
|
||||
*/
|
||||
function generateRegistrationEmailHTML(data) {
|
||||
|
||||
// Helper to cleanly format boolean true/false fields
|
||||
const formatBool = (val) => {
|
||||
if (val === true || val === 'true') return 'Yes';
|
||||
if (val === false || val === 'false') return 'No';
|
||||
return val;
|
||||
};
|
||||
|
||||
// Helper to ensure we don't render literal 'null' or 'undefined' text
|
||||
const sanitizeValue = (val) => {
|
||||
if (val === null || val === undefined) return '';
|
||||
return String(val).trim();
|
||||
};
|
||||
|
||||
// Define the layout sections and human-readable labels
|
||||
const sections = [
|
||||
{
|
||||
title: "Student Information",
|
||||
fields: [
|
||||
{ label: "Student Name", value: `${sanitizeValue(data.firstName)} ${sanitizeValue(data.lastName)}`.trim() },
|
||||
{ label: "Grade", value: sanitizeValue(data.grade) },
|
||||
{ label: "Date of Birth", value: sanitizeValue(data.dob) },
|
||||
{ label: "Has Allergies?", value: sanitizeValue(formatBool(data.hasAllergies)) },
|
||||
{ label: "Allergies", value: sanitizeValue(data.allergies) },
|
||||
{ label: "Has Dietary Restrictions?", value: sanitizeValue(formatBool(data.hasDietaryRestrictions)) },
|
||||
{ label: "Dietary Restrictions", value: sanitizeValue(data.dietaryRestrictions) },
|
||||
{ label: "Attended in Past?", value: sanitizeValue(formatBool(data.attendedInPast)) },
|
||||
{ label: "Rides Bus?", value: sanitizeValue(formatBool(data.ridesBus)) }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Parent / Guardian Contact",
|
||||
fields: [
|
||||
{ label: "Parent Name", value: `${sanitizeValue(data.parentFirstName)} ${sanitizeValue(data.parentLastName)}`.trim() },
|
||||
{ label: "Email", value: sanitizeValue(data.parentEmail) },
|
||||
{ label: "Mobile Phone", value: sanitizeValue(data.parentMobilePhone) },
|
||||
{ label: "Home Phone", value: sanitizeValue(data.parentHomePhone) },
|
||||
{ label: "Work Phone", value: sanitizeValue(data.parentWorkPhone) },
|
||||
{ label: "Address", value: `${sanitizeValue(data.address1)}${data.address2 ? ', ' + sanitizeValue(data.address2) : ''}, ${sanitizeValue(data.city)}, ${sanitizeValue(data.state)} ${sanitizeValue(data.zip)}`.trim().replace(/^,|,$/g, '') }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Emergency Contact",
|
||||
fields: [
|
||||
{ label: "Same as Parent?", value: sanitizeValue(formatBool(data.emergencySameAsParent)) },
|
||||
{ label: "Emergency Name", value: `${sanitizeValue(data.emergencyFirstName)} ${sanitizeValue(data.emergencyLastName)}`.trim() },
|
||||
{ label: "Emergency Phone", value: sanitizeValue(data.emergencyPhone) }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Map the sections array into HTML tables (No filtering out fields!)
|
||||
const htmlContent = sections.map(section => {
|
||||
const rows = section.fields.map(field => `
|
||||
<tr>
|
||||
<td style="padding: 10px 12px; border-bottom: 1px solid #edf2f7; font-weight: 600; color: #4a5568; width: 35%; font-size: 14px;">
|
||||
${field.label}
|
||||
</td>
|
||||
<td style="padding: 10px 12px; border-bottom: 1px solid #edf2f7; color: #2d3748; font-size: 14px;">
|
||||
${field.value}
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
return `
|
||||
<h3 style="color: #4f46e5; font-size: 16px; margin-top: 24px; margin-bottom: 10px; border-bottom: 2px solid #e2e8f0; padding-bottom: 6px; font-weight: 600;">
|
||||
${section.title}
|
||||
</h3>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse; margin-bottom: 15px;">
|
||||
<tbody>
|
||||
${rows}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>New Registration Submission</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #f7fafc; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f7fafc; padding: 24px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 650px; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.05); border: 1px solid #e2e8f0;">
|
||||
|
||||
<!-- Header Banner -->
|
||||
<tr>
|
||||
<td style="background-color: #4f46e5; padding: 24px; text-align: center;">
|
||||
<h1 style="color: #ffffff; margin: 0; font-size: 20px; font-weight: 600; letter-spacing: 0.5px;">
|
||||
New Registration Submitted
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content Body -->
|
||||
<tr>
|
||||
<td style="padding: 24px 32px;">
|
||||
<p style="margin-top: 0; margin-bottom: 16px; font-size: 15px; color: #718096; line-height: 1.5;">
|
||||
A new form submission has been received.
|
||||
</p>
|
||||
|
||||
${htmlContent}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f8fafc; padding: 20px; text-align: center; font-size: 12px; color: #a0aec0; border-top: 1px solid #edf2f7;">
|
||||
This email was automatically generated and sent from the registration system.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
Loading…
Reference in New Issue