Compare commits
2 Commits
b9bb46c216
...
contact-mo
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c14d771d6 | |||
| abc192887f |
7
frontend/package-lock.json
generated
7
frontend/package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"@angular/forms": "^20.3.0",
|
"@angular/forms": "^20.3.0",
|
||||||
"@angular/platform-browser": "^20.3.0",
|
"@angular/platform-browser": "^20.3.0",
|
||||||
"@angular/router": "^20.3.0",
|
"@angular/router": "^20.3.0",
|
||||||
|
"@hugeicons/core-free-icons": "^2.0.0",
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
@@ -1356,6 +1357,12 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hugeicons/core-free-icons": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-OSfv5k0iB0yG61dcfK7jcf00AIK8EXyQOgtcNJzSBFvm88n9VOelkxihZHJnNwDUFpO/jZI3vZSVp6i1dmRvJQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@inquirer/ansi": {
|
"node_modules/@inquirer/ansi": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"@angular/forms": "^20.3.0",
|
"@angular/forms": "^20.3.0",
|
||||||
"@angular/platform-browser": "^20.3.0",
|
"@angular/platform-browser": "^20.3.0",
|
||||||
"@angular/router": "^20.3.0",
|
"@angular/router": "^20.3.0",
|
||||||
|
"@hugeicons/core-free-icons": "^2.0.0",
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
|
|||||||
@@ -66,37 +66,7 @@
|
|||||||
<div class="grid md:grid-cols-4 gap-8">
|
<div class="grid md:grid-cols-4 gap-8">
|
||||||
<div *ngFor="let plan of plans"
|
<div *ngFor="let plan of plans"
|
||||||
[class]="'relative flex flex-col bg-slate-800/50 backdrop-blur-sm rounded-xl p-8 border-2 transition-all hover:transform hover:scale-105 ' + (plan.popular ? 'border-green-500 shadow-xl shadow-green-500/20' : 'border-slate-700 hover:border-green-500')">
|
[class]="'relative flex flex-col bg-slate-800/50 backdrop-blur-sm rounded-xl p-8 border-2 transition-all hover:transform hover:scale-105 ' + (plan.popular ? 'border-green-500 shadow-xl shadow-green-500/20' : 'border-slate-700 hover:border-green-500')">
|
||||||
<div class="flex-grow-1">
|
<app-price-card [plan]="plan" />
|
||||||
<div *ngIf="plan.popular"
|
|
||||||
class="absolute -top-4 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-4 py-1 rounded-full text-sm font-bold">
|
|
||||||
Most Popular
|
|
||||||
</div>
|
|
||||||
<div class="text-center mb-6">
|
|
||||||
<h3 class="text-2xl font-bold text-white mb-2">{{ plan.name }}</h3>
|
|
||||||
<div class="text-5xl font-bold text-white mb-2">{{ plan.price }}</div>
|
|
||||||
<div class="text-gray-400">/month</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-3 mb-8">
|
|
||||||
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
|
|
||||||
<div class="font-bold text-white">{{ plan.players }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
|
|
||||||
<div class="font-bold text-white">{{ plan.ram }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
|
|
||||||
<div class="font-bold text-white">{{ plan.storage }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="space-y-3 mb-8">
|
|
||||||
<li *ngFor="let feature of plan.features" class="flex items-center text-gray-300">
|
|
||||||
<svg class="w-5 h-5 text-green-400 mr-2 flex-shrink-0" fill="none" stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
||||||
</svg>
|
|
||||||
{{ feature }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
[class]="'w-full py-3 rounded-lg font-bold transition-all ' + (plan.popular ? 'bg-green-500 hover:bg-green-600 text-white' : 'bg-slate-700 hover:bg-slate-600 text-white')">
|
[class]="'w-full py-3 rounded-lg font-bold transition-all ' + (plan.popular ? 'bg-green-500 hover:bg-green-600 text-white' : 'bg-slate-700 hover:bg-slate-600 text-white')">
|
||||||
@@ -123,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<app-footer />
|
<app-footer [(contactOpen)]="contactOpen" />
|
||||||
|
|
||||||
<app-contact-modal />
|
<app-contact-modal [(open)]="contactOpen"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, OnDestroy, OnInit, HostListener } from '@angular/core';
|
import { Component, OnDestroy, OnInit, HostListener, signal } from '@angular/core';
|
||||||
import { Navigation } from './components/navigation/navigation';
|
import { Navigation } from './components/navigation/navigation';
|
||||||
import { Feature } from './interfaces/feature';
|
import { Feature } from './interfaces/feature';
|
||||||
import { Plan } from './interfaces/plan';
|
import { Plan } from './interfaces/plan';
|
||||||
import { Stat } from './interfaces/stat';
|
import { Stat } from './interfaces/stat';
|
||||||
import { Footer } from './components/footer/footer';
|
import { Footer } from './components/footer/footer';
|
||||||
import { ContactModal } from './contact-modal/contact-modal';
|
import { ContactModal } from './components/contact-modal/contact-modal';
|
||||||
|
import { PriceCard } from './components/price-card/price-card';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [CommonModule, Navigation, Footer, ContactModal],
|
imports: [CommonModule, Navigation, Footer, ContactModal, PriceCard],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.css'
|
styleUrl: './app.css'
|
||||||
})
|
})
|
||||||
export class App implements OnInit, OnDestroy {
|
export class App implements OnInit, OnDestroy {
|
||||||
|
contactOpen = signal<boolean>(false);
|
||||||
mobileMenuOpen = false;
|
mobileMenuOpen = false;
|
||||||
scrolled = false;
|
scrolled = false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
|
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" [class.hidden]="!open()"
|
||||||
(ngClick)="toggleOpen()">
|
(click)="toggleOpen()">
|
||||||
<div class="relative w-full max-w-lg bg-gray-900 rounded-3xl border border-gray-800 p-8 shadow-2xl"
|
<div class="relative w-full max-w-lg bg-gray-900 rounded-3xl border border-gray-800 p-8 shadow-2xl"
|
||||||
(ngClick)="toggleOpen()">
|
(click)="toggleOpen()">
|
||||||
<button (ngClick)="toggleOpen()"
|
<button (click)="toggleOpen()"
|
||||||
class="absolute top-6 right-6 w-10 h-10 rounded-full border border-gray-700 flex items-center justify-center hover:border-violet-500 hover:text-violet-500 transition-all">
|
class="absolute top-6 right-6 w-10 h-10 rounded-full border border-gray-700 flex items-center justify-center hover:border-green-500 hover:text-green-500 transition-all">
|
||||||
<!-- <X class="w-5 h-5" /> -->
|
<!-- <X class="w-5 h-5" /> -->
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<h2 class="text-3xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-violet-400 to-pink-400">
|
<h2 class="text-3xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-green-400 to-pink-400">
|
||||||
Get In Touch
|
Get In Touch
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-400 mb-8">
|
<p class="text-gray-400 mb-8">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="name" name="name" formControlName="name" required
|
<input type="text" id="name" name="name" formControlName="name" required
|
||||||
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-violet-500 transition-colors text-white"
|
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white"
|
||||||
placeholder="Your name" />
|
placeholder="Your name" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input type="email" id="email" name="email" formControlName="email" required
|
<input type="email" id="email" name="email" formControlName="email" required
|
||||||
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-violet-500 transition-colors text-white"
|
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white"
|
||||||
placeholder="your@email.com" />
|
placeholder="your@email.com" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -38,15 +38,15 @@
|
|||||||
Message
|
Message
|
||||||
</label>
|
</label>
|
||||||
<input type="text" formControlName="message" required
|
<input type="text" formControlName="message" required
|
||||||
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-violet-500 transition-colors text-white resize-none"
|
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white resize-none"
|
||||||
placeholder="Tell me something" />
|
placeholder="Tell me something" />
|
||||||
<!-- <textarea id="message" name="message" formControlName="message" required rows="5"
|
<!-- <textarea id="message" name="message" formControlName="message" required rows="5"
|
||||||
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-violet-500 transition-colors text-white resize-none"
|
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white resize-none"
|
||||||
placeholder="Tell me about your project..."> -->
|
placeholder="Tell me about your project..."> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="w-full px-8 py-4 bg-gradient-to-r from-violet-600 to-purple-600 rounded-xl font-semibold hover:scale-[1.02] transition-transform flex items-center justify-center gap-2">
|
class="w-full px-8 py-4 bg-gradient-to-r from-green-300 to-green-500 rounded-xl font-semibold hover:scale-[1.02] transition-transform flex items-center justify-center gap-2">
|
||||||
Send Message
|
Send Message
|
||||||
<!-- <ArrowRight class="w-5 h-5" /> -->
|
<!-- <ArrowRight class="w-5 h-5" /> -->
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula
|
|||||||
styles: ``,
|
styles: ``,
|
||||||
})
|
})
|
||||||
export class ContactModal {
|
export class ContactModal {
|
||||||
open = model<boolean>(false);
|
open = model.required<boolean>();
|
||||||
|
|
||||||
contactForm = new FormGroup({
|
contactForm = new FormGroup({
|
||||||
name: new FormControl(''),
|
name: new FormControl(''),
|
||||||
@@ -17,7 +17,7 @@ export class ContactModal {
|
|||||||
}, { validators: Validators.required })
|
}, { validators: Validators.required })
|
||||||
|
|
||||||
toggleOpen() {
|
toggleOpen() {
|
||||||
this.open.update(value => !value);
|
this.open.update(prev => !prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="flex justify-center space-x-6 text-sm">
|
<div class="flex justify-center space-x-6 text-sm">
|
||||||
<a href="#" class="hover:text-white transition">Terms</a>
|
<a href="#" class="hover:text-white transition">Terms</a>
|
||||||
<a href="#" class="hover:text-white transition">Privacy</a>
|
<a href="#" class="hover:text-white transition">Privacy</a>
|
||||||
<a href="#" class="hover:text-white transition">Contact</a>
|
<a (click)="toggleContactModal()" class="hover:text-white transition">Contact</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, model } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-footer',
|
selector: 'app-footer',
|
||||||
@@ -7,5 +7,9 @@ import { Component } from '@angular/core';
|
|||||||
styles: ``,
|
styles: ``,
|
||||||
})
|
})
|
||||||
export class Footer {
|
export class Footer {
|
||||||
|
contactOpen = model.required<boolean>();
|
||||||
|
|
||||||
|
toggleContactModal() {
|
||||||
|
this.contactOpen.update(prev => !prev);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
frontend/src/app/components/price-card/price-card.html
Normal file
30
frontend/src/app/components/price-card/price-card.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<div class="flex-grow-1">
|
||||||
|
<div *ngIf="plan().popular"
|
||||||
|
class="absolute -top-4 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-4 py-1 rounded-full text-sm font-bold">
|
||||||
|
Most Popular
|
||||||
|
</div>
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<h3 class="text-2xl font-bold text-white mb-2">{{ plan().name }}</h3>
|
||||||
|
<div class="text-5xl font-bold text-white mb-2">{{ plan().price }}</div>
|
||||||
|
<div class="text-gray-400">/month</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3 mb-8">
|
||||||
|
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
|
||||||
|
<div class="font-bold text-white">{{ plan().players }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
|
||||||
|
<div class="font-bold text-white">{{ plan().ram }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
|
||||||
|
<div class="font-bold text-white">{{ plan().storage }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-3 mb-8">
|
||||||
|
<li *ngFor="let feature of plan().features" class="flex items-center text-gray-300">
|
||||||
|
<svg class="w-5 h-5 text-green-400 mr-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
{{ feature }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
23
frontend/src/app/components/price-card/price-card.spec.ts
Normal file
23
frontend/src/app/components/price-card/price-card.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PriceCard } from './price-card';
|
||||||
|
|
||||||
|
describe('PriceCard', () => {
|
||||||
|
let component: PriceCard;
|
||||||
|
let fixture: ComponentFixture<PriceCard>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [PriceCard]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(PriceCard);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
13
frontend/src/app/components/price-card/price-card.ts
Normal file
13
frontend/src/app/components/price-card/price-card.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Component, input } from '@angular/core';
|
||||||
|
import { Plan } from '../../interfaces/plan';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-price-card',
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './price-card.html',
|
||||||
|
styles: ``,
|
||||||
|
})
|
||||||
|
export class PriceCard {
|
||||||
|
plan = input.required<Plan>();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user