From 5326e545a880a47c79f491aae3007163c8bd6873 Mon Sep 17 00:00:00 2001 From: Vasa Date: Tue, 19 Aug 2025 18:44:53 +0300 Subject: [PATCH] WIP --- angular.json | 10 +- package-lock.json | 36 +- package.json | 4 +- src/app/app.component.html | 352 +----------------- src/app/app.component.ts | 11 - src/app/app.routes.ts | 6 +- src/app/candidate-data.service.ts | 19 + .../image-input/image-input.component.html | 28 ++ .../image-input/image-input.component.scss | 49 +++ src/app/image-input/image-input.component.ts | 47 +++ .../registration/registration.component.html | 115 ++++++ .../registration/registration.component.scss | 164 ++++++++ .../registration/registration.component.ts | 86 +++++ src/environments/environment.development.ts | 1 + src/environments/environment.ts | 1 + src/index.html | 2 + src/styles.scss | 3 + 17 files changed, 571 insertions(+), 363 deletions(-) create mode 100644 src/app/candidate-data.service.ts create mode 100644 src/app/image-input/image-input.component.html create mode 100644 src/app/image-input/image-input.component.scss create mode 100644 src/app/image-input/image-input.component.ts create mode 100644 src/app/registration/registration.component.html create mode 100644 src/app/registration/registration.component.scss create mode 100644 src/app/registration/registration.component.ts create mode 100644 src/environments/environment.development.ts create mode 100644 src/environments/environment.ts diff --git a/angular.json b/angular.json index f0cb141..662396c 100644 --- a/angular.json +++ b/angular.json @@ -32,6 +32,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/magenta-violet.css", "src/styles.scss" ], "scripts": [] @@ -55,7 +56,13 @@ "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] } }, "defaultConfiguration": "production" @@ -91,6 +98,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/magenta-violet.css", "src/styles.scss" ], "scripts": [] diff --git a/package-lock.json b/package-lock.json index b84e356..044b5e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^19.1.0", + "@angular/cdk": "^19.2.19", "@angular/common": "^19.1.0", "@angular/compiler": "^19.1.0", "@angular/core": "^19.1.0", "@angular/forms": "^19.1.0", + "@angular/material": "^19.2.19", "@angular/platform-browser": "^19.1.0", "@angular/platform-browser-dynamic": "^19.1.0", "@angular/router": "^19.1.0", @@ -510,6 +512,21 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/@angular/cdk": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.19.tgz", + "integrity": "sha512-PCpJagurPBqciqcq4Z8+3OtKLb7rSl4w/qBJoIMua8CgnrjvA1i+SWawhdtfI1zlY8FSwhzLwXV0CmWWfFzQPg==", + "license": "MIT", + "dependencies": { + "parse5": "^7.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "19.2.15", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.15.tgz", @@ -683,6 +700,23 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.19.tgz", + "integrity": "sha512-auIE6JUzTIA3LyYklh9J/T7u64crmphxUBgAa0zcOMDog6SYfwbNe9YeLQqua5ek4OUAOdK/BHHfVl5W5iaUoQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "19.2.19", + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "@angular/forms": "^19.0.0 || ^20.0.0", + "@angular/platform-browser": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.14.tgz", @@ -11277,7 +11311,6 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -11318,7 +11351,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" diff --git a/package.json b/package.json index ab3d1d7..6652cc0 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,12 @@ "private": true, "dependencies": { "@angular/animations": "^19.1.0", + "@angular/cdk": "^19.2.19", "@angular/common": "^19.1.0", "@angular/compiler": "^19.1.0", "@angular/core": "^19.1.0", "@angular/forms": "^19.1.0", + "@angular/material": "^19.2.19", "@angular/platform-browser": "^19.1.0", "@angular/platform-browser-dynamic": "^19.1.0", "@angular/router": "^19.1.0", @@ -35,4 +37,4 @@ "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.7.2" } -} +} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 7073f36..e9e03c7 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,347 +1,5 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - -@if(this.error){ -

response:

-

{{ this.error }}

-} -@if (this.response) { -

response:

-

{{ this.response }}

-} - - - +
+
+
+ +
\ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 27363b6..7c7babe 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -9,15 +9,4 @@ import { RouterOutlet } from '@angular/router'; styleUrl: './app.component.scss' }) export class AppComponent { - title = 'IISA_web'; - httpClient = inject(HttpClient); - response: any | null = null; - error: any | null = null; - - sendRequest(){ - this.httpClient.get('http://localhost:3000/app/candidates').subscribe({ - next: (res) => this.response = JSON.stringify(res), - error: err => this.error = JSON.stringify(err) - }) - } } diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index dc39edb..3b98114 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,3 +1,7 @@ import { Routes } from '@angular/router'; +import { RegistrationComponent } from './registration/registration.component'; -export const routes: Routes = []; +export const routes: Routes = [ + { path: 'register', component: RegistrationComponent }, + { path: '', redirectTo: '/register', pathMatch: 'full' }, +]; diff --git a/src/app/candidate-data.service.ts b/src/app/candidate-data.service.ts new file mode 100644 index 0000000..5b9ec89 --- /dev/null +++ b/src/app/candidate-data.service.ts @@ -0,0 +1,19 @@ +import { HttpClient } from "@angular/common/http"; +import { inject, Injectable } from "@angular/core"; + +@Injectable({ + providedIn: 'root' +}) +export class CandidateDataService { + httpClient = inject(HttpClient) + + response: any | null = null; + error: any | null = null; + + sendRequest() { + this.httpClient.get('http://localhost:3000/app/candidates').subscribe({ + next: (res) => this.response = JSON.stringify(res), + error: err => this.error = JSON.stringify(err) + }) + } +} \ No newline at end of file diff --git a/src/app/image-input/image-input.component.html b/src/app/image-input/image-input.component.html new file mode 100644 index 0000000..b0fd17e --- /dev/null +++ b/src/app/image-input/image-input.component.html @@ -0,0 +1,28 @@ +
+

Angular material image input

+
+ + Photo +
+ @if(editForm.get('photo')!.value){ + + + } + + @if(!editForm.get('photo')!.value){ +
+ file_upload + + Drag and drop here +
+ } + + +
+ +
+
+
\ No newline at end of file diff --git a/src/app/image-input/image-input.component.scss b/src/app/image-input/image-input.component.scss new file mode 100644 index 0000000..6a3e299 --- /dev/null +++ b/src/app/image-input/image-input.component.scss @@ -0,0 +1,49 @@ +.fileUploadContainer { + padding: 10px; + display: flex; + flex-direction: column; + margin: 0 auto; + width: 150px; + height: 150px; + border: dashed 1px #979797; + text-align: center; + justify-content: center; + + img { + display: block; + margin-left: auto; + margin-right: auto; + max-height: 100%; + max-width: 100%; + } + + .noImageContainet { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 11px; + button { + font-size: 11px; + } + } + + .deleteButton{ + position: absolute; + z-index: 10; + top: -25px; + inset-inline-end: -10px; + opacity:50% + } + + .fileInput { + position: absolute; + z-index: 9; + opacity: 0; + height: 100%; + width: 100%; + left: 0px; + top: 0px; + cursor: pointer; + } + } \ No newline at end of file diff --git a/src/app/image-input/image-input.component.ts b/src/app/image-input/image-input.component.ts new file mode 100644 index 0000000..2d75d56 --- /dev/null +++ b/src/app/image-input/image-input.component.ts @@ -0,0 +1,47 @@ +import { Component, inject } from '@angular/core'; +import { FormsModule, ReactiveFormsModule, UntypedFormBuilder } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatIconModule } from '@angular/material/icon'; +import { CommonModule } from '@angular/common'; + + + + +@Component({ + selector: 'app-image-input', + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule + ], + templateUrl: './image-input.component.html', + styleUrl: './image-input.component.scss' +}) +export class ImageInputComponent { + + fb = inject(UntypedFormBuilder); + + editForm = this.fb.group({ + photo: [] + }); + + + setFileData(event: Event): void { + const eventTarget: HTMLInputElement | null = event.target as HTMLInputElement | null; + if (eventTarget?.files?.[0]) { + const file: File = eventTarget.files[0]; + const reader = new FileReader(); + reader.addEventListener('load', () => { + this.editForm.get('photo')?.setValue(reader.result as string); + }); + reader.readAsDataURL(file); + } + } +} + diff --git a/src/app/registration/registration.component.html b/src/app/registration/registration.component.html new file mode 100644 index 0000000..7029104 --- /dev/null +++ b/src/app/registration/registration.component.html @@ -0,0 +1,115 @@ +
+

🚀 Spaceflight Candidate Registration

+ +
+ + + + + Full Name + + @if (form.get('fullName')?.hasError('required')) { + + Name is required + + } + + + + Email + + @if (form.get('email')?.hasError('email')) { + + Invalid email + + } + + + + Phone Number + + @if (form.get('phone')?.hasError('required')) { + + Phone number is required + + } + @if (form.get('phone')?.hasError('pattern')){ + + Invalid phone number + + } + + + + Age + + @if (form.get('age')?.hasError('min') || form.get('age')?.hasError('max')) { + + Only aplicants of age 18 - 70 allowed + + } + + + + City / Region + + @if (form.get('city')?.hasError('required')) { + + Field is required + + } + + + + Hobbies + + @if (form.get('hobbies')?.hasError('maxLength')) { + + Maximum length is 300 characters + + } + + + + Why I am the perfect candidate + + @if (form.get('reason')?.hasError('required')) { + + Field is required + + } + @if (form.get('reason')?.hasError('maxLength')) { + + Maximum length is 300 characters + + } + + + + + + +
+
\ No newline at end of file diff --git a/src/app/registration/registration.component.scss b/src/app/registration/registration.component.scss new file mode 100644 index 0000000..5b2c22a --- /dev/null +++ b/src/app/registration/registration.component.scss @@ -0,0 +1,164 @@ +/* Cosmic-themed container styling */ +.registration-container { + max-width: 900px; + margin: 2rem auto; + padding: 2.5rem; + background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); + color: #e0e0e0; + border-radius: 16px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); + position: relative; + overflow: hidden; +} + +/* Cosmic background overlay */ +.registration-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 70%); + opacity: 0.3; + pointer-events: none; +} + +/* Form title with space theme */ +.title { + text-align: center; + font-size: 2.2rem; + font-weight: 700; + color: #00d4ff; + margin-bottom: 2.5rem; + text-transform: uppercase; + letter-spacing: 3px; + text-shadow: 0 0 12px rgba(0, 212, 255, 0.6); + animation: glow 2s ease-in-out infinite alternate; +} + +/* Glowing title animation */ +@keyframes glow { + from { + text-shadow: 0 0 8px rgba(0, 212, 255, 0.4); + } + to { + text-shadow: 0 0 16px rgba(0, 212, 255, 0.8); + } +} + +/* Form styling */ +.form { + display: flex; + flex-direction: column; + gap: 2rem; +} + +/* Full-width form fields */ +.full-width { + width: 100%; +} + +// /* Material form field customization */ +// ::ng-deep .mat-form-field { +// .mat-form-field-label { +// color: #b0bec5; +// font-weight: 500; +// } + +// .mat-form-field-underline { +// background-color: #00d4ff !important; +// } + +// .mat-form-field-ripple { +// background-color: #00d4ff !important; +// } + +// input, textarea { +// color: #e0e0e0; +// background-color: rgba(255, 255, 255, 0.08); +// border-radius: 6px; +// padding: 0.75rem; +// transition: background-color 0.3s ease; +// } + +// input:focus, textarea:focus { +// background-color: rgba(255, 255, 255, 0.12); +// } + +// textarea { +// resize: vertical; +// min-height: 80px; +// } +// } + +/* Error message styling */ +// ::ng-deep .mat-error { +// color: #ff6b6b; +// font-size: 0.9rem; +// } + +/* Upload section */ +.upload-section { + align-items: center; + margin: 2rem 0; + display: flex; + flex-direction: column; + gap: 1.5rem; + + .preview { + margin-top: 1.5rem; + } + + img { + width: 250px; + height: 250px; + border: 4px solid #00d4ff; + box-shadow: 0 0 20px rgba(0, 212, 255, 0.4); + transition: transform 0.3s ease, box-shadow 0.3s ease; + } + + img:hover { + transform: scale(1.1); + box-shadow: 0 0 25px rgba(0, 212, 255, 0.6); + } +} + +/* Responsive design */ +@media (max-width: 768px) { + .registration-container { + margin: 1.5rem; + padding: 2rem; + } + + .title { + font-size: 1.8rem; + margin-bottom: 2rem; + } + + .upload-section img { + max-width: 140px; + } + + // ::ng-deep .mat-raised-button { + // padding: 0.75rem 1.5rem; + // font-size: 0.95rem; + // } +} + +@media (max-width: 480px) { + .registration-container { + margin: 1rem; + padding: 1.5rem; + } + + .title { + font-size: 1.4rem; + margin-bottom: 1.5rem; + } + + .upload-section img { + max-width: 120px; + } + +} \ No newline at end of file diff --git a/src/app/registration/registration.component.ts b/src/app/registration/registration.component.ts new file mode 100644 index 0000000..14aefa5 --- /dev/null +++ b/src/app/registration/registration.component.ts @@ -0,0 +1,86 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; + +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { ImageInputComponent } from "../image-input/image-input.component"; + +const israeliPhoneRegex = /^(?:(?:(\+?972|\(\+?972\)|\+?\(972\))(?:\s|\.|-)?([1-9]\d?))|(0[23489]{1})|(0[57]{1}[0-9]))(?:\s|\.|-)?([^0\D]{1}\d{2}(?:\s|\.|-)?\d{4})$/; +@Component({ + selector: 'app-registration', + imports: [ + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatButtonModule, + MatCardModule, + MatFormFieldModule, + ImageInputComponent +], + templateUrl: './registration.component.html', + styleUrls: ['./registration.component.scss'], +}) +export class RegistrationComponent implements OnInit { + + fb = inject(FormBuilder); + previewUrl: string | ArrayBuffer | null = null; + + form = this.fb.group({ + fullName: ['', [Validators.required, Validators.minLength(3)]], + email: ['', [Validators.required, Validators.email]], + phone: ['', [Validators.required, Validators.pattern(israeliPhoneRegex)]], + age: [0, [Validators.required, Validators.min(18), Validators.max(70)]], + city: ['', Validators.required], + hobbies: ['', [Validators.maxLength(300)]], + reason: ['', [Validators.required, Validators.maxLength(300)]], + profileImage: this.fb.control(null, Validators.required), + }); + + ngOnInit(): void { + const savedData = localStorage.getItem('registration'); + if (!savedData) return; + + const parsed = JSON.parse(savedData); + const dayMilliseconds = 1000 * 60 * 60 * 24; + if (Date.now() - parsed.timestamp < dayMilliseconds * 3) { + this.form.patchValue(parsed.data); + this.previewUrl = parsed.data.profileImage; + } else { + localStorage.removeItem('registration'); + } + } + + onFileSelected(event: any) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = () => { + if (typeof reader.result === 'string') { + this.previewUrl = reader.result; + this.form.patchValue({ profileImage: reader.result }); + } + }; + reader.readAsDataURL(file); + } + } + + onSubmit() { + if (this.form.valid) { + localStorage.setItem( + 'registration', + JSON.stringify({ + data: this.form.value, + timestamp: Date.now(), + }) + ); + alert('✅ Application saved! You can re-edit within 3 days.'); + } + } + + onCheckErrors() { + console.log(this.form.controls.age.errors); + } +} diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts new file mode 100644 index 0000000..f274e5e --- /dev/null +++ b/src/environments/environment.development.ts @@ -0,0 +1 @@ +export const environment = {}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 0000000..f274e5e --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1 @@ +export const environment = {}; diff --git a/src/index.html b/src/index.html index bf097f0..6cd995a 100644 --- a/src/index.html +++ b/src/index.html @@ -6,6 +6,8 @@ + + diff --git a/src/styles.scss b/src/styles.scss index 90d4ee0..7e7239a 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }