diff --git a/firebase.json b/firebase.json index 6889100..6e01456 100644 --- a/firebase.json +++ b/firebase.json @@ -13,6 +13,26 @@ } ] }, + "database": { + "rules": "database.rules.json" + }, + "functions": [ + { + "source": "functions", + "logLevel": "debug", + "codebase": "default", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ], + "predeploy": [ + "npm --prefix \"$RESOURCE_DIR\" run lint", + "npm --prefix \"$RESOURCE_DIR\" run build" + ] + } + ], "storage": { "rules": "storage.rules" } diff --git a/functions/.eslintrc.js b/functions/.eslintrc.js index 5790a6a..854a7ad 100644 --- a/functions/.eslintrc.js +++ b/functions/.eslintrc.js @@ -5,28 +5,28 @@ module.exports = { node: true, }, extends: [ - "eslint:recommended", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:import/typescript", - "google", - "plugin:@typescript-eslint/recommended", + 'eslint:recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + 'google', + 'plugin:@typescript-eslint/recommended', ], - parser: "@typescript-eslint/parser", + parser: '@typescript-eslint/parser', parserOptions: { - project: ["tsconfig.json", "tsconfig.dev.json"], - sourceType: "module", + project: ['tsconfig.json', 'tsconfig.dev.json'], + sourceType: 'module', }, ignorePatterns: [ - "/lib/**/*", // Ignore built files. + '/lib/**/*', // Ignore built files. ], plugins: [ - "@typescript-eslint", - "import", + '@typescript-eslint', + 'import', ], rules: { - "quotes": ["error", "single"], - "import/no-unresolved": 0, - "indent": ["error", 2], + 'quotes': ['error', 'single'], + 'import/no-unresolved': 0, + 'indent': ['error', 2], }, }; diff --git a/functions/src/index.ts b/functions/src/index.ts index 3248f9f..223c1e5 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,5 +3,5 @@ require('dotenv').config(); import {initializeApp} from 'firebase-admin/app'; initializeApp(); -export {sendContactMessage} from './sendgrid'; +export {sendContactMessageV2} from './sendgrid'; export {checkRecaptcha} from './recaptcha'; diff --git a/functions/src/recaptcha.ts b/functions/src/recaptcha.ts index 98a3955..6819452 100644 --- a/functions/src/recaptcha.ts +++ b/functions/src/recaptcha.ts @@ -10,7 +10,8 @@ export const checkRecaptcha = onRequest((req, res) => { // 'http://localhost:8080' res.set('Access-Control-Allow-Origin', 'https://anthonybuzzelli.dev'); res.setHeader('Content-Type', 'application/json'); - const token = req.query.token; + const token = req.body.token; + // recommended from Claude const token = req.body.token console.log(token, 'what is here'); try { const response = await axios.get(`https://recaptcha.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${token}`); diff --git a/functions/src/sendgrid.ts b/functions/src/sendgrid.ts index 6981300..6e2acc1 100644 --- a/functions/src/sendgrid.ts +++ b/functions/src/sendgrid.ts @@ -5,7 +5,7 @@ const sgMail = require('@sendgrid/mail'); const SENDGRID_API_KEY = process.env.SENDGRID_API_KEY; sgMail.setApiKey(SENDGRID_API_KEY); -export const sendContactMessage = onValueWritten( +export const sendContactMessageV2 = onValueWritten( 'messages/{pushkey}', async (change) => { const dataAfterChange = change.data.after.val(); if (change.data.before.val() || !dataAfterChange.subject) { diff --git a/src/app/animations/nav-animation.ts b/src/app/animations/nav-animation.ts new file mode 100644 index 0000000..094bebd --- /dev/null +++ b/src/app/animations/nav-animation.ts @@ -0,0 +1,52 @@ +import { NavOptions, createAnimation } from '@ionic/core'; + +interface TransitionOptions extends NavOptions { + progressCallback?: (ani: Animation | undefined) => void; + baseEl: any; + enteringEl: HTMLElement; + // leavingEl: HTMLElement | undefined; + leavingEl: HTMLElement; +} + +function getIonPageElement(element: HTMLElement) { + if (element.classList.contains('ion-page')) { + return element; + } + + const ionPage = element.querySelector( + ':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs' + ); + if (ionPage) { + return ionPage; + } + + return element; +} + +export function pageTransition(_: HTMLElement, opts: TransitionOptions) { + const DURATION = 600; + + const rootTransition = createAnimation() + .duration(opts.duration || DURATION) + .easing('cubic-bezier(0.3,0,0.66,1)'); + + const enteringPage = createAnimation() + .addElement(getIonPageElement(opts.enteringEl)) + .beforeRemoveClass('ion-page-invisible'); + + const leavingPage = createAnimation().addElement( + getIonPageElement(opts.leavingEl) + ); + + if (opts.direction === 'forward') { + enteringPage.fromTo('transform', 'translateX(100%)', 'translateX(0)'); + leavingPage.fromTo('opacity', '1', '0.25'); + } else { + leavingPage.fromTo('transform', 'translateX(0)', 'translateX(100%)'); + enteringPage.fromTo('opacity', '0.25', '1'); + } + + rootTransition.addAnimation(enteringPage); + rootTransition.addAnimation(leavingPage); + return rootTransition; +} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index b37a044..edde976 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -2,9 +2,7 @@ - Inbox - hi@ionicframework.com - + Anthony Buzzelli @@ -12,16 +10,9 @@ - - - Labels - - - - {{ label }} - - + + diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 0aeb6fe..3a1f063 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -29,17 +29,13 @@ ion-menu.md ion-list#inbox-list { ion-menu.md ion-list#inbox-list ion-list-header { font-size: 22px; font-weight: 600; - min-height: 20px; } ion-menu.md ion-list#labels-list ion-list-header { font-size: 16px; - margin-bottom: 18px; - color: #757575; - min-height: 26px; } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1ba91b4..59805df 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,33 +1,37 @@ import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { RouterLink, RouterLinkActive } from '@angular/router'; -import { IonApp, IonSplitPane, IonMenu, IonContent, IonList, IonListHeader, IonNote, IonMenuToggle, IonItem, IonIcon, IonLabel, IonRouterOutlet } from '@ionic/angular/standalone'; +import { IonApp, IonMenu, IonContent, IonList, IonListHeader, IonNote, IonMenuToggle, IonItem, IonIcon, IonLabel, IonRouterOutlet } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { mailOutline, mailSharp, paperPlaneOutline, paperPlaneSharp, heartOutline, heartSharp, archiveOutline, archiveSharp, trashOutline, trashSharp, warningOutline, warningSharp, bookmarkOutline, bookmarkSharp } from 'ionicons/icons'; +import { LoadingComponent } from './loading/loading.component'; @Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.scss'], standalone: true, - imports: [RouterLink, RouterLinkActive, CommonModule, IonApp, IonSplitPane, IonMenu, IonContent, IonList, IonListHeader, IonNote, IonMenuToggle, IonItem, IonIcon, IonLabel, IonRouterOutlet], + imports: [RouterLink, RouterLinkActive, CommonModule, IonApp, IonMenu, IonContent, IonList, IonListHeader, IonNote, IonMenuToggle, IonItem, IonIcon, IonLabel, IonRouterOutlet, LoadingComponent], }) export class AppComponent { + // loading = true; + // private router = inject(Router); public appPages = [ - {title:'Home', url: '/home', icon: 'mail'}, + { title:'Home', url: '/home', icon: 'mail' }, { title: 'About', url: '/about', icon: 'mail' }, { title: 'Projects', url: '/projects', icon: 'mail' }, - { title: 'Contact', url: '/contact', icon: 'mail' }, - - { title: 'Inbox', url: '/folder/inbox', icon: 'mail' }, - { title: 'Outbox', url: '/folder/outbox', icon: 'paper-plane' }, - { title: 'Favorites', url: '/folder/favorites', icon: 'heart' }, - { title: 'Archived', url: '/folder/archived', icon: 'archive' }, - { title: 'Trash', url: '/folder/trash', icon: 'trash' }, - { title: 'Spam', url: '/folder/spam', icon: 'warning' }, - ]; - public labels = ['Family', 'Friends', 'Notes', 'Work', 'Travel', 'Reminders']; + { title: 'Contact', url: '/contact', icon: 'mail' } + ] + constructor() { addIcons({ mailOutline, mailSharp, paperPlaneOutline, paperPlaneSharp, heartOutline, heartSharp, archiveOutline, archiveSharp, trashOutline, trashSharp, warningOutline, warningSharp, bookmarkOutline, bookmarkSharp }); + + // this.router.events.subscribe(event => { + // if (event instanceof NavigationStart) { + // this.loading = true; + // } else if (event instanceof NavigationEnd) { + // this.loading = false; + // } + // }) } } diff --git a/src/app/contact/contact.component.ts b/src/app/contact/contact.component.ts index d43cd56..344cbad 100644 --- a/src/app/contact/contact.component.ts +++ b/src/app/contact/contact.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit, inject } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { CommonModule } from '@angular/common'; -// import { HttpClient } from '@angular/common/http'; -// import { ReCaptchaV3Service } from 'ng-recaptcha'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { ReCaptchaV3Service } from 'ng-recaptcha'; import { Router } from '@angular/router'; import { Database, push, ref, set } from '@angular/fire/database'; import { UIService } from '../ui.service'; @@ -21,9 +21,9 @@ import { FooterComponent } from '../footer/footer.component'; export class ContactComponent implements OnInit { private db = inject(Database) private uiService = inject(UIService) - // private http = inject(HttpClient) + private http = inject(HttpClient) private router = inject(Router) - // private recaptchaService = inject(ReCaptchaV3Service) + private recaptchaService = inject(ReCaptchaV3Service) constructor() { } @@ -58,32 +58,69 @@ export class ContactComponent implements OnInit { } onSubmit() { - this.isSubmitted = true - const value = this.contactForm.value; - const name = value.name; - const email = value.email; - const subject = value.subject; - const message = value.message; - + // this.isSubmitted = true + // const value = this.contactForm.value; + // const name = value.name; + // const email = value.email; + // const subject = value.subject; + // const message = value.message; + this.recaptchaService.execute('contact') + .subscribe((token) => { + console.log(token, 'token') + this.verifyRecaptcha(token); + }) - const formRequest = { name, email, subject, message}; - const messagesRef = ref(this.db, '/messages'); - const newMessageRef = push(messagesRef); - set(newMessageRef, {...formRequest}) - .then(() => { - // console.log(formRequest, 'What is showing here'); - this.contactForm.reset(); - this.uiService.presentToast('Your message was sent', 4000); - this.router.navigate(['/']); - }) - .catch((error) => { - // console.log(error, 'Error in sending message'); - this.uiService.presentToast('Error in sending message', 4000); - this.contactForm.reset(); - this.isSubmitted = false; - this.router.navigate(['/']); - throw error; - }); + // const formRequest = { name, email, subject, message}; + // const messagesRef = ref(this.db, '/messages'); + // const newMessageRef = push(messagesRef); + // set(newMessageRef, {...formRequest}) + // .then(() => { + // // console.log(formRequest, 'What is showing here'); + // this.contactForm.reset(); + // this.uiService.presentToast('Your message was sent', 4000); + // this.router.navigate(['/']); + // }) + // .catch((error) => { + // // console.log(error, 'Error in sending message'); + // this.uiService.presentToast('Error in sending message', 4000); + // this.contactForm.reset(); + // this.isSubmitted = false; + // this.router.navigate(['/']); + // throw error; + // }); + } + + verifyRecaptcha(token: string) { + const url = 'https://us-central1-ionicwebpage.cloudfunctions.net/checkRecaptcha'; + const headers = new HttpHeaders().set('Content-Type', 'application/json') + + this.http.post(url, { token }, { headers }).subscribe((response) => { + console.log(response, 'data') + this.isSubmitted = true + const value = this.contactForm.value; + const name = value.name; + const email = value.email; + const subject = value.subject; + const message = value.message; + const formRequest = { name, email, subject, message }; + const messagesRef = ref(this.db, '/messages'); + const newMessageRef = push(messagesRef); + set(newMessageRef, { ...formRequest }) + .then(() => { + // console.log(formRequest, 'What is showing here'); + this.contactForm.reset(); + this.uiService.presentToast('Your message was sent', 4000); + this.router.navigate(['/']); + }) + .catch((error) => { + // console.log(error, 'Error in sending message'); + this.uiService.presentToast('Error in sending message', 4000); + this.contactForm.reset(); + this.isSubmitted = false; + this.router.navigate(['/']); + throw error; + }); + }) } } diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 8f04b72..0fa47ae 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -3,7 +3,9 @@ import { AfterViewChecked, AfterViewInit, Component, ElementRef, HostListener, O import { IonHeader, IonToolbar, IonButtons, IonMenuButton, IonTitle, IonContent } from '@ionic/angular/standalone'; import { HeaderComponent } from '../header/header.component'; -import * as THREE from 'three' +import * as THREE from 'three'; +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +// import model from '../../assets/facefull.glb' @Component({ selector: 'app-home', @@ -24,12 +26,15 @@ export class HomeComponent implements OnInit, AfterViewChecked, OnDestroy { private rendererInitialized = false; // public home!: string; // private activatedRoute = inject(ActivatedRoute); + private model: any; constructor() { // this.render = this.render.bind(this); } ngOnInit() { + const loader = new GLTFLoader() + this.model = '../../assets/facefull.glb' // this.home = this.activatedRoute.snapshot.paramMap.get('id') as string; this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); @@ -67,9 +72,26 @@ export class HomeComponent implements OnInit, AfterViewChecked, OnDestroy { fragmentShader: fragmentShader }); this.cube = new THREE.Mesh(geometry, material); - this.scene.add(this.cube); + // this.scene.add(this.cube); console.log(this.cube, 'cube') console.log('this is running', this.renderer) + + // this.loader = new GLTFLoader() + loader.load(this.model,(gltf)=>{ + this.model = gltf.scene.children[0]; + this.model.position.set(0, -1, -1.5); + this.model.rotation.set(0, 0, 0); + this.model.scale.set(4000,2000,2000); + console.log(this.model.scale); + this.scene.add(this.model); + this.model.traverse((o: { isMesh: any; material: THREE.MeshBasicMaterial; })=>{ + if(o.isMesh){ + console.log(o); + o.material = new THREE.MeshBasicMaterial({color:0xff0000}) + } + }); + console.log(this.model, 'model running?') + }); } ngAfterViewChecked() { diff --git a/src/app/loading/loading.component.html b/src/app/loading/loading.component.html new file mode 100644 index 0000000..4d11774 --- /dev/null +++ b/src/app/loading/loading.component.html @@ -0,0 +1,5 @@ + +
+
Dude Something
+
+
\ No newline at end of file diff --git a/src/app/loading/loading.component.scss b/src/app/loading/loading.component.scss new file mode 100644 index 0000000..8ec15b4 --- /dev/null +++ b/src/app/loading/loading.component.scss @@ -0,0 +1,17 @@ +.loading-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #fff; + z-index: 9999; +} + +.loading-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + /* Add your loading icon styles here */ +} \ No newline at end of file diff --git a/src/app/loading/loading.component.spec.ts b/src/app/loading/loading.component.spec.ts new file mode 100644 index 0000000..b0cc5a5 --- /dev/null +++ b/src/app/loading/loading.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { LoadingComponent } from './loading.component'; + +describe('LoadingComponent', () => { + let component: LoadingComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ LoadingComponent ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(LoadingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loading/loading.component.ts b/src/app/loading/loading.component.ts new file mode 100644 index 0000000..ef708ca --- /dev/null +++ b/src/app/loading/loading.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { IonContent } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-loading', + templateUrl: './loading.component.html', + styleUrls: ['./loading.component.scss'], + standalone: true, + imports: [IonContent] +}) +export class LoadingComponent { + + constructor() { } + + + +} diff --git a/src/assets/facefull.glb b/src/assets/facefull.glb new file mode 100644 index 0000000..e3f73f9 Binary files /dev/null and b/src/assets/facefull.glb differ diff --git a/src/main.ts b/src/main.ts index 42c91d4..d2586a7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,8 @@ import { enableProdMode, importProvidersFrom } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { RouteReuseStrategy, provideRouter } from '@angular/router'; import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalone'; - +import { HttpClientModule } from '@angular/common/http'; +import { provideAnimations } from '@angular/platform-browser/animations' import { routes } from './app/app.routes'; import { AppComponent } from './app/app.component'; import { environment } from './environments/environment'; @@ -14,6 +15,8 @@ import { getStorage, provideStorage } from '@angular/fire/storage'; import { RECAPTCHA_V3_SITE_KEY, RecaptchaV3Module } from "ng-recaptcha"; +import { pageTransition } from './app/animations/nav-animation'; + if (environment.production) { enableProdMode(); } @@ -21,12 +24,14 @@ if (environment.production) { bootstrapApplication(AppComponent, { providers: [ { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, - provideIonicAngular(), + provideIonicAngular({ navAnimation: pageTransition }), provideRouter(routes), + provideAnimations(), importProvidersFrom(provideFirebaseApp(() => initializeApp({"projectId":"ionicwebpage","appId":"1:952994598736:web:c051e724ce521f59cca655","databaseURL":"https://ionicwebpage.firebaseio.com","storageBucket":"ionicwebpage.appspot.com","apiKey":"AIzaSyBytj8gvFINALswEUnSwtUBBRoDfUuQDJw","authDomain":"ionicwebpage.firebaseapp.com","messagingSenderId":"952994598736"}))), importProvidersFrom(provideAnalytics(() => getAnalytics())), ScreenTrackingService, importProvidersFrom(provideDatabase(() => getDatabase())), importProvidersFrom(provideFunctions(() => getFunctions())), importProvidersFrom(provideStorage(() => getStorage())), - importProvidersFrom(RecaptchaV3Module), { provide: RECAPTCHA_V3_SITE_KEY, useValue: '6Lc5l8gpAAAAAFQXvzUkcbYTXVvj4UZKkJB_NwV-' } + importProvidersFrom(RecaptchaV3Module), { provide: RECAPTCHA_V3_SITE_KEY, useValue: '6Lc5l8gpAAAAAFQXvzUkcbYTXVvj4UZKkJB_NwV-' }, + importProvidersFrom(HttpClientModule) ], });