code cleanup + finished contact page
This commit is contained in:
38
package-lock.json
generated
38
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "portfolio",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "portfolio",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^15.1.0",
|
||||
"@angular/cdk": "^15.1.4",
|
||||
@@ -20,12 +20,13 @@
|
||||
"@angular/platform-server": "^15.1.0",
|
||||
"@angular/router": "^15.1.0",
|
||||
"@nguniversal/express-engine": "^15.1.0",
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@sweetalert2/theme-dark": "^5.0.15",
|
||||
"chart.js": "^4.2.1",
|
||||
"express": "^4.15.2",
|
||||
"ngx-device-detector": "^5.0.1",
|
||||
"pocketbase": "^0.11.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"sweetalert2": "^11.7.2",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.12.0"
|
||||
},
|
||||
@@ -34,6 +35,7 @@
|
||||
"@angular/cli": "~15.1.5",
|
||||
"@angular/compiler-cli": "^15.1.0",
|
||||
"@nguniversal/builders": "^15.1.0",
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"@types/node": "^14.15.0",
|
||||
@@ -3931,6 +3933,11 @@
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sweetalert2/theme-dark": {
|
||||
"version": "5.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.15.tgz",
|
||||
"integrity": "sha512-g1QCwQVOkiAz5hIEBOIvvu0580lubu4KuQlod+48QetYzGIEXNlHEH36QihCDnGVgE6vx48iO48w9q0WrZWyHQ=="
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
@@ -3962,6 +3969,7 @@
|
||||
"version": "2.9.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.37.tgz",
|
||||
"integrity": "sha512-9bosRfHhkXxKYfrw94EmyDQcdjMaQPkU1fH2tDxu8DWXxf1mjzWQAV4laJF51ZbC2ycYwNDvIm1rGez8Bug0vg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"moment": "^2.10.2"
|
||||
}
|
||||
@@ -9933,6 +9941,7 @@
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
@@ -12444,6 +12453,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2": {
|
||||
"version": "11.7.2",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.2.tgz",
|
||||
"integrity": "sha512-atPjDa3fv/4xwZpiAt7FZUgAhR5VAASiLP2hu7HUeVDXx+v4/9nD1W0u8xal1e9f2/qGh0DwTxPXPV9XoZIBvg==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/limonte"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-observable": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||
@@ -16544,6 +16562,11 @@
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"@sweetalert2/theme-dark": {
|
||||
"version": "5.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.15.tgz",
|
||||
"integrity": "sha512-g1QCwQVOkiAz5hIEBOIvvu0580lubu4KuQlod+48QetYzGIEXNlHEH36QihCDnGVgE6vx48iO48w9q0WrZWyHQ=="
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
@@ -16572,6 +16595,7 @@
|
||||
"version": "2.9.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.37.tgz",
|
||||
"integrity": "sha512-9bosRfHhkXxKYfrw94EmyDQcdjMaQPkU1fH2tDxu8DWXxf1mjzWQAV4laJF51ZbC2ycYwNDvIm1rGez8Bug0vg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"moment": "^2.10.2"
|
||||
}
|
||||
@@ -21214,7 +21238,8 @@
|
||||
"moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
@@ -23111,6 +23136,11 @@
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true
|
||||
},
|
||||
"sweetalert2": {
|
||||
"version": "11.7.2",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.2.tgz",
|
||||
"integrity": "sha512-atPjDa3fv/4xwZpiAt7FZUgAhR5VAASiLP2hu7HUeVDXx+v4/9nD1W0u8xal1e9f2/qGh0DwTxPXPV9XoZIBvg=="
|
||||
},
|
||||
"symbol-observable": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "portfolio",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
@@ -26,12 +26,13 @@
|
||||
"@angular/platform-server": "^15.1.0",
|
||||
"@angular/router": "^15.1.0",
|
||||
"@nguniversal/express-engine": "^15.1.0",
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@sweetalert2/theme-dark": "^5.0.15",
|
||||
"chart.js": "^4.2.1",
|
||||
"express": "^4.15.2",
|
||||
"ngx-device-detector": "^5.0.1",
|
||||
"pocketbase": "^0.11.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"sweetalert2": "^11.7.2",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.12.0"
|
||||
},
|
||||
@@ -40,6 +41,7 @@
|
||||
"@angular/cli": "~15.1.5",
|
||||
"@angular/compiler-cli": "^15.1.0",
|
||||
"@nguniversal/builders": "^15.1.0",
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"@types/node": "^14.15.0",
|
||||
|
||||
@@ -3,11 +3,15 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
import {HomeComponent} from "./sites/home/home.component";
|
||||
import {ProjectsComponent} from "./sites/projects/projects.component";
|
||||
import {TechnologiesComponent} from "./sites/technologies/technologies.component";
|
||||
import {AboutComponent} from "./sites/about/about.component";
|
||||
import {ContactComponent} from "./sites/contact/contact.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "", component: HomeComponent},
|
||||
{path: "projects", component: ProjectsComponent},
|
||||
{path: "technologies", component: TechnologiesComponent},
|
||||
{path: "about", component: AboutComponent},
|
||||
{path: "contact", component: ContactComponent},
|
||||
{path: "**", pathMatch: "full", redirectTo: ""}
|
||||
];
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { NavigationComponent } from './components/navigation/navigation.componen
|
||||
import {MatIconModule} from "@angular/material/icon";
|
||||
import {MatButtonModule} from "@angular/material/button";
|
||||
import { HomeComponent } from './sites/home/home.component';
|
||||
import { FeaturedProjectsPipe } from './pipes/featured-projects.pipe';
|
||||
import { FeaturedPipe } from './pipes/featured.pipe';
|
||||
import { ProjectsComponent } from './sites/projects/projects.component';
|
||||
import {MatTooltipModule} from "@angular/material/tooltip";
|
||||
import { FancyButtonComponent } from './components/fancy-button/fancy-button.component';
|
||||
@@ -18,13 +18,17 @@ import { TechnologyComponent } from './components/technology/technology.componen
|
||||
import { LanguagesPipe } from './pipes/languages.pipe';
|
||||
import { FrameworksPipe } from './pipes/frameworks.pipe';
|
||||
import { SkillsPipe } from './pipes/skills.pipe';
|
||||
import { ContactComponent } from './sites/contact/contact.component';
|
||||
import { AboutComponent } from './sites/about/about.component';
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
NavigationComponent,
|
||||
HomeComponent,
|
||||
FeaturedProjectsPipe,
|
||||
FeaturedPipe,
|
||||
ProjectsComponent,
|
||||
FancyButtonComponent,
|
||||
ProjectComponent,
|
||||
@@ -32,7 +36,9 @@ import { SkillsPipe } from './pipes/skills.pipe';
|
||||
TechnologyComponent,
|
||||
LanguagesPipe,
|
||||
FrameworksPipe,
|
||||
SkillsPipe
|
||||
SkillsPipe,
|
||||
ContactComponent,
|
||||
AboutComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({appId: 'serverApp'}),
|
||||
@@ -40,7 +46,9 @@ import { SkillsPipe } from './pipes/skills.pipe';
|
||||
BrowserAnimationsModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatTooltipModule
|
||||
MatTooltipModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
$border-color: #2d2d2d;
|
||||
@use "src/theme";
|
||||
@use "sass:map";
|
||||
|
||||
.header {
|
||||
width: 100vw;
|
||||
height: 50px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
border-bottom: 1px solid theme.$border-color;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
user-select: none;
|
||||
@@ -67,7 +68,8 @@ $border-color: #2d2d2d;
|
||||
transform: translateY(-100%);
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
border-top: 1px solid $border-color;
|
||||
border-top: 1px solid theme.$border-color;
|
||||
background-color: map.get(theme.$background, 'background');
|
||||
|
||||
display: grid;
|
||||
grid-auto-columns: minmax(0, 1fr);
|
||||
@@ -85,7 +87,7 @@ $border-color: #2d2d2d;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
content: '';
|
||||
border-right: 1px solid $border-color;
|
||||
border-right: 1px solid theme.$border-color;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {DeviceDetectorService} from "ngx-device-detector";
|
||||
import {Router} from "@angular/router";
|
||||
import {BackendService} from "../../services/backend.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-navigation',
|
||||
templateUrl: './navigation.component.html',
|
||||
styleUrls: ['./navigation.component.scss']
|
||||
})
|
||||
export class NavigationComponent {
|
||||
export class NavigationComponent implements OnInit {
|
||||
|
||||
public constructor(public deviceService: DeviceDetectorService, public router: Router) {}
|
||||
public constructor(public deviceService: DeviceDetectorService, public router: Router, private backend: BackendService) {}
|
||||
|
||||
public navLinks: {label: string, href: string, icon?: string}[] = [
|
||||
{label: 'Home', href: '/', icon: 'home'},
|
||||
@@ -19,11 +20,7 @@ export class NavigationComponent {
|
||||
{label: 'Kontakt', href: '/contact', icon: 'mail'}
|
||||
];
|
||||
|
||||
public socialLinks: {href: string, image: string}[] = [
|
||||
{href: 'https://www.instagram.com/leonh.23/', image: 'https://instagram.com/favicon.ico'},
|
||||
{href: 'https://git.leon-hoppe.de/leon.hoppe', image: 'https://git.leon-hoppe.de/favicon.ico'},
|
||||
{href: 'mailto://leon@ladenbau-hoppe.de', image: 'https://webmail.strato.de/favicon.ico'}
|
||||
];
|
||||
public socialLinks: {href: string, image: string}[];
|
||||
|
||||
public cleanUrl(url: string): string {
|
||||
try {
|
||||
@@ -39,4 +36,8 @@ export class NavigationComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.socialLinks = await this.backend.getSocials();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
5
src/app/models/message.ts
Normal file
5
src/app/models/message.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface Message {
|
||||
name: string;
|
||||
email: string;
|
||||
message: string;
|
||||
}
|
||||
4
src/app/models/social.ts
Normal file
4
src/app/models/social.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Social {
|
||||
href: string;
|
||||
image: string;
|
||||
}
|
||||
5
src/app/models/timestamp.ts
Normal file
5
src/app/models/timestamp.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface Timestamp {
|
||||
date: number,
|
||||
description: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {Project} from "../models/project";
|
||||
|
||||
@Pipe({
|
||||
name: 'featuredProjects'
|
||||
})
|
||||
export class FeaturedProjectsPipe implements PipeTransform {
|
||||
|
||||
transform(objects: Project[]): Project[] {
|
||||
const newObjects: Project[] = [];
|
||||
objects?.forEach(obj => {
|
||||
if (obj?.featured)
|
||||
newObjects.push(obj);
|
||||
})
|
||||
return newObjects;
|
||||
}
|
||||
|
||||
}
|
||||
12
src/app/pipes/featured.pipe.ts
Normal file
12
src/app/pipes/featured.pipe.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'featured'
|
||||
})
|
||||
export class FeaturedPipe implements PipeTransform {
|
||||
|
||||
transform(objects: (any & {featured: boolean})[]): any[] {
|
||||
return objects?.filter(obj => obj.featured);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,12 +7,7 @@ import {Technology} from "../models/technology";
|
||||
export class FrameworksPipe implements PipeTransform {
|
||||
|
||||
transform(objects: Technology[]): Technology[] {
|
||||
const newObjects: Technology[] = [];
|
||||
objects?.forEach(obj => {
|
||||
if (obj?.type == "Framework")
|
||||
newObjects.push(obj);
|
||||
})
|
||||
return newObjects;
|
||||
return objects?.filter(obj => obj.type == "Framework");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,12 +7,7 @@ import {Technology} from "../models/technology";
|
||||
export class LanguagesPipe implements PipeTransform {
|
||||
|
||||
transform(objects: Technology[]): Technology[] {
|
||||
const newObjects: Technology[] = [];
|
||||
objects?.forEach(obj => {
|
||||
if (obj?.type == "Language")
|
||||
newObjects.push(obj);
|
||||
})
|
||||
return newObjects;
|
||||
return objects?.filter(obj => obj.type == "Language");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,12 +7,7 @@ import {Technology} from "../models/technology";
|
||||
export class SkillsPipe implements PipeTransform {
|
||||
|
||||
transform(objects: Technology[]): Technology[] {
|
||||
const newObjects: Technology[] = [];
|
||||
objects?.forEach(obj => {
|
||||
if (obj?.type == "Additional")
|
||||
newObjects.push(obj);
|
||||
})
|
||||
return newObjects;
|
||||
return objects?.filter(obj => obj.type == "Additional");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { Injectable } from '@angular/core';
|
||||
import PocketBase from 'pocketbase';
|
||||
import {Language, Project} from "../models/project";
|
||||
import {Technology} from "../models/technology";
|
||||
import {Timestamp} from "../models/timestamp";
|
||||
import {Social} from "../models/social";
|
||||
import {Message} from "../models/message";
|
||||
|
||||
|
||||
@Injectable({
|
||||
@@ -46,4 +49,20 @@ export class BackendService {
|
||||
return await this.pb?.collection('technologies').getFullList();
|
||||
}
|
||||
|
||||
public async getTimeline(): Promise<Timestamp[]> {
|
||||
return await this.pb?.collection('timeline').getFullList();
|
||||
}
|
||||
|
||||
public async getSocials(): Promise<Social[]> {
|
||||
return [
|
||||
{href: 'https://www.instagram.com/leonh.23/', image: 'https://instagram.com/favicon.ico'},
|
||||
{href: 'https://git.leon-hoppe.de/leon.hoppe', image: 'https://git.leon-hoppe.de/favicon.ico'},
|
||||
{href: 'mailto://leon@ladenbau-hoppe.de', image: 'https://webmail.strato.de/favicon.ico'}
|
||||
];
|
||||
}
|
||||
|
||||
public async sendMessage(message: Message) {
|
||||
await this.pb?.collection('messages').create(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1
src/app/sites/about/about.component.html
Normal file
1
src/app/sites/about/about.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>about works!</p>
|
||||
0
src/app/sites/about/about.component.scss
Normal file
0
src/app/sites/about/about.component.scss
Normal file
10
src/app/sites/about/about.component.ts
Normal file
10
src/app/sites/about/about.component.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about',
|
||||
templateUrl: './about.component.html',
|
||||
styleUrls: ['./about.component.scss']
|
||||
})
|
||||
export class AboutComponent {
|
||||
|
||||
}
|
||||
41
src/app/sites/contact/contact.component.html
Normal file
41
src/app/sites/contact/contact.component.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<div class="hider" [ngClass]="{'mobile': device.isMobile()}">
|
||||
<div class="form-wrapper" [ngClass]="{'mobile': device.isMobile()}">
|
||||
<form [formGroup]="form" (ngSubmit)="sendMessage()">
|
||||
<section id="contact-info">
|
||||
<h1>Kontakt</h1>
|
||||
<span>leon@ladenbau-hoppe.de</span>
|
||||
<span>+49 1575 8839776</span>
|
||||
</section>
|
||||
|
||||
<section id="contact-form">
|
||||
<h1>Schreiben Sie mir!</h1>
|
||||
|
||||
<div id="fields">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput formControlName="name">
|
||||
<mat-error>Name ist erforderlich</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Email</mat-label>
|
||||
<input matInput formControlName="email">
|
||||
<mat-error *ngIf="form.hasError('required', 'email')">E-Mail ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('email', 'email') && !form.hasError('required', 'email')">
|
||||
Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Nachricht</mat-label>
|
||||
<textarea matInput formControlName="message"></textarea>
|
||||
<mat-error>Nachricht ist erforderlich</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<button mat-button type="submit">Senden</button>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
89
src/app/sites/contact/contact.component.scss
Normal file
89
src/app/sites/contact/contact.component.scss
Normal file
@@ -0,0 +1,89 @@
|
||||
@use "src/theme";
|
||||
|
||||
.hider {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
&.mobile {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
form {
|
||||
width: 700px;
|
||||
height: 500px;
|
||||
border: 1px solid theme.$border-color;
|
||||
border-radius: 20px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
|
||||
section {
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 20px;
|
||||
font-size: 30px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#contact-info {
|
||||
border-right: 1px solid theme.$border-color;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
#contact-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
#fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
gap: 10px;
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: auto;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
display: block;
|
||||
height: max-content;
|
||||
|
||||
form {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
grid-template-columns: unset;
|
||||
grid-template-rows: max-content 1fr;
|
||||
|
||||
#contact-info {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid theme.$border-color;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/app/sites/contact/contact.component.ts
Normal file
40
src/app/sites/contact/contact.component.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
||||
import Swal from 'sweetalert2/dist/sweetalert2.js';
|
||||
import {BackendService} from "../../services/backend.service";
|
||||
import {DeviceDetectorService} from "ngx-device-detector";
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact',
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: ['./contact.component.scss']
|
||||
})
|
||||
export class ContactComponent {
|
||||
|
||||
public form: FormGroup = new FormGroup({
|
||||
name: new FormControl('', [Validators.required]),
|
||||
email: new FormControl('', [Validators.required, Validators.email]),
|
||||
message: new FormControl('', [Validators.required])
|
||||
});
|
||||
|
||||
public constructor(public backend: BackendService, public device: DeviceDetectorService) {}
|
||||
|
||||
public async sendMessage() {
|
||||
if (!this.form.valid) return;
|
||||
|
||||
await this.backend.sendMessage({
|
||||
name: this.form.get('name').value,
|
||||
email: this.form.get('email').value,
|
||||
message: this.form.get('message').value
|
||||
});
|
||||
this.form.reset();
|
||||
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Nachricht gesendet',
|
||||
showConfirmButton: false,
|
||||
timer: 1500
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
<h1 class="title">Projekte</h1>
|
||||
<a routerLink="/projects">alle ansehen</a>
|
||||
<div id="projects-wrapper" #projectsWrapper>
|
||||
<app-project *ngFor="let project of projects | featuredProjects; let i = index" [project]="project" [ngStyle]="{'animation-delay': getAnimationDelay(i)}" />
|
||||
<app-project *ngFor="let project of projects | featured; let i = index" [project]="project" [ngStyle]="{'animation-delay': getAnimationDelay(i)}" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<h1 class="title">Technologien</h1>
|
||||
<a routerLink="/technologies">mehr erfahren</a>
|
||||
<div class="technologies-wrapper">
|
||||
<app-technology *ngFor="let technology of technologies" [technology]="technology" />
|
||||
<app-technology *ngFor="let technology of technologies | featured" [technology]="technology" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<h1 class="title">Über mich</h1>
|
||||
<a routerLink="/about">mehr erfahren</a>
|
||||
<div id="timeline" #timelineElement>
|
||||
<div class="timestamp" *ngFor="let timestamp of timeline; let i = index" [ngStyle]="{'--delay': getAnimationDelay(i, 500)}">
|
||||
<div class="timestamp" *ngFor="let timestamp of timeline | featured; let i = index" [ngStyle]="{'--delay': getAnimationDelay(i, 500)}">
|
||||
<h2>{{timestamp.date}}</h2>
|
||||
<span>{{timestamp.description}}</span>
|
||||
</div>
|
||||
|
||||
@@ -115,8 +115,10 @@
|
||||
}
|
||||
|
||||
span {
|
||||
box-sizing: border-box;
|
||||
color: theme.$desc-color;
|
||||
font-size: 14px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
&:after {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {Project} from "../../models/project";
|
||||
import {Technology} from "../../models/technology";
|
||||
import {BackendService} from "../../services/backend.service";
|
||||
import {AnimatorService} from "../../services/animator.service";
|
||||
import {Timestamp} from "../../models/timestamp";
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
@@ -16,29 +17,20 @@ export class HomeComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild('timelineElement') timelineElement: ElementRef;
|
||||
public projects: Project[];
|
||||
public technologies: Technology[];
|
||||
public timeline: Timestamp[];
|
||||
public socialLinks: {href: string, image: string}[];
|
||||
|
||||
public constructor(public deviceService: DeviceDetectorService, private backend: BackendService, private animator: AnimatorService) {}
|
||||
|
||||
public timeline: {date: number, description: string}[] = [
|
||||
{date: 2010, description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur excepturi facere, fuga maxime nulla qui voluptas voluptates? Adipisci asperiores dolor error iste sunt tempore. Blanditiis illum mollitia nostrum quae vero?"},
|
||||
{date: 2015, description: "Lorem ipsum dolor sit amet"},
|
||||
{date: 2017, description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur excepturi facere, fuga maxime nulla qui voluptas voluptates? Adipisci asperiores dolor error iste sunt tempore. Blanditiis illum mollitia nostrum quae vero?"},
|
||||
{date: 2022, description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur excepturi facere, fuga maxime nulla qui voluptas voluptates? Adipisci asperiores dolor error iste sunt tempore. Blanditiis illum mollitia nostrum quae vero?"},
|
||||
];
|
||||
|
||||
public socialLinks: {href: string, image: string}[] = [
|
||||
{href: 'https://www.instagram.com/leonh.23/', image: 'https://instagram.com/favicon.ico'},
|
||||
{href: 'https://git.leon-hoppe.de/leon.hoppe', image: 'https://git.leon-hoppe.de/favicon.ico'},
|
||||
{href: 'mailto://leon@ladenbau-hoppe.de', image: 'https://webmail.strato.de/favicon.ico'}
|
||||
];
|
||||
|
||||
public getAnimationDelay(index: number, multiplier = 150): string {
|
||||
return `${index * multiplier}ms`;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.projects = await this.backend.getProjects();
|
||||
this.technologies = (await this.backend.getTechnologies()).filter(tech => tech.featured);
|
||||
this.technologies = await this.backend.getTechnologies();
|
||||
this.timeline = await this.backend.getTimeline();
|
||||
this.socialLinks = await this.backend.getSocials();
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
<h1 class="title">Technologien in Projekten</h1>
|
||||
<div class="chart">
|
||||
<div class="chart-container"><canvas #chard></canvas></div>
|
||||
<div class="chart-container"><canvas #chart></canvas></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {DeviceDetectorService} from "ngx-device-detector";
|
||||
})
|
||||
export class TechnologiesComponent implements AfterViewInit {
|
||||
|
||||
@ViewChild('chard') chartRef: ElementRef;
|
||||
@ViewChild('chart') chartRef: ElementRef;
|
||||
public technologies: Technology[];
|
||||
|
||||
public constructor(public deviceService: DeviceDetectorService, private backend: BackendService) {}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@use "sass:map";
|
||||
@use "theme";
|
||||
|
||||
@import '@sweetalert2/theme-dark/dark.scss';
|
||||
|
||||
html, body { height: 100vh; }
|
||||
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||
*, html {scroll-behavior: smooth !important;}
|
||||
|
||||
@@ -52,6 +52,7 @@ $padding: 12.5vw;
|
||||
$padding-small: 5vw;
|
||||
|
||||
$desc-color: #7c8393;
|
||||
$border-color: #2d2d2d;
|
||||
|
||||
$color-config: mat.get-color-config($angular-theme);
|
||||
$background: map.get($color-config, 'background');
|
||||
@@ -84,3 +85,7 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-text-field-wrapper {
|
||||
background-color: map.get($background, 'background') !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user