working good
Some checks failed
Build and Deploy to k3s / build-and-deploy (push) Failing after 38s

This commit is contained in:
Alexis Bruneteau 2025-06-04 21:52:54 +02:00
parent 1fdf7516a5
commit 49e1bcac81
6 changed files with 171 additions and 158 deletions

View File

@ -13,12 +13,14 @@
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-semibold text-gray-700 truncate">{{ p.name }}</h3> <h3 class="text-lg font-semibold text-gray-700 truncate">{{ p.name }}</h3>
<span class="text-xs px-2 py-1 rounded-full" <span class="text-xs px-2 py-1 rounded-full"
[ngClass]="p.path ? 'bg-green-100 text-green-600' : 'bg-yellow-100 text-yellow-600'"> [ngClass]="!p.active ? 'bg-red-100 text-red-600' :
{{ p.path ? 'Uploaded' : 'Pending Upload' }} p.path ? 'bg-green-100 text-green-600' :
'bg-yellow-100 text-yellow-600'">
{{ !p.active ? 'Pending Payment' : (p.path ? 'Uploaded' : 'Pending Upload') }}
</span> </span>
</div> </div>
<a href="https://{{ p.domain }}.portfolio-host.com" class=" text-sm text-gray-500 break-words mb-4">{{ p.domain }}.portfolio-host.com</a> <a href="https://{{ p.domain }}" class=" text-sm text-gray-500 break-words mb-4">{{ p.domain }}</a>
<div class="flex flex-col md:flex-row items-center justify-between gap-4 mt-4"> <div class="flex flex-col md:flex-row items-center justify-between gap-4 mt-4">

View File

@ -1,7 +1,7 @@
<div class="min-h-screen bg-gray-50 text-gray-800 font-sans"> <div class="min-h-screen bg-gray-50 text-gray-800 font-sans">
<!-- Hero Section --> <!-- Hero Section -->
<section <section
class="bg-white py-20 px-6 text-center md:text-left md:px-20 flex flex-col md:flex-row items-center justify-between"> class="bg-white py-20 px-6 text-center md:text-left md:px-20 flex flex-col flex items-center justify-between">
<div class="max-w-xl"> <div class="max-w-xl">
<h1 class="text-4xl md:text-6xl font-bold leading-tight mb-4"> <h1 class="text-4xl md:text-6xl font-bold leading-tight mb-4">
Host Your Portfolio. <br class="hidden md:block"> Impress the World. Host Your Portfolio. <br class="hidden md:block"> Impress the World.
@ -13,15 +13,13 @@
<button (click)=signUp() class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-xl shadow"> <button (click)=signUp() class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-xl shadow">
Start Free Trial Start Free Trial
</button> </button>
<button <button (click)=exemple()
class="border border-gray-300 hover:border-blue-600 text-blue-600 hover:text-blue-700 font-semibold py-2 px-4 rounded-xl"> class="border border-gray-300 hover:border-blue-600 text-blue-600 hover:text-blue-700 font-semibold py-2 px-4 rounded-xl">
See Examples See Examples
</button> </button>
</div> </div>
</div> </div>
<div class="mt-12 md:mt-0 md:ml-12 w-full md:w-1/2">
<img alt="Portfolio Illustration" class="w-full h-auto" src="assets/hero-illustration.svg">
</div>
</section> </section>
<!-- Features Section --> <!-- Features Section -->
@ -29,26 +27,21 @@
<div class="text-center mb-12"> <div class="text-center mb-12">
<h2 class="text-3xl md:text-4xl font-bold">Why Choose PortfolioHost?</h2> <h2 class="text-3xl md:text-4xl font-bold">Why Choose PortfolioHost?</h2>
</div> </div>
<div class="grid gap-10 md:grid-cols-4 text-center"> <div class="flex justify-center gap-10 md:grid-cols-4 text-center">
<div> <div>
<div class="text-4xl mb-4"></div> <div class="text-3xl mb-3"></div>
<h3 class="font-semibold text-xl mb-2">Blazing Fast Hosting</h3> <h3 class="font-semibold text-xl mb-2">Blazing Fast Hosting</h3>
<p class="text-gray-600">NVMe servers & global CDN ensure speed and uptime.</p> <p class="text-gray-600">NVMe servers & lightning fast deployment pipeline.</p>
</div> </div>
<div> <div>
<div class="text-4xl mb-4">🌐</div> <div class="text-3xl mb-3">🌐</div>
<h3 class="font-semibold text-xl mb-2">Custom Domains</h3> <h3 class="font-semibold text-xl mb-2">Free Domains</h3>
<p class="text-gray-600">Use your own domain or get a .portfoliohost.dev address.</p> <p class="text-gray-600">Get a free .portfolio-host.com address.</p>
</div> </div>
<div> <div>
<div class="text-4xl mb-4">🚀</div> <div class="text-3xl mb-3">🚀</div>
<h3 class="font-semibold text-xl mb-2">1-Click Deploy</h3> <h3 class="font-semibold text-xl mb-2">1-Click Deploy</h3>
<p class="text-gray-600">Integrate with GitHub or push manually in seconds.</p> <p class="text-gray-600">Integrate with GitHub (COMING SOON) or push manually in seconds.</p>
</div>
<div>
<div class="text-4xl mb-4">📱</div>
<h3 class="font-semibold text-xl mb-2">Fully Responsive</h3>
<p class="text-gray-600">Mobile-first designs that look great everywhere.</p>
</div> </div>
</div> </div>
</section> </section>
@ -56,7 +49,7 @@
<!-- Final CTA --> <!-- Final CTA -->
<section class="bg-blue-600 py-16 px-6 md:px-20 text-white text-center"> <section class="bg-blue-600 py-16 px-6 md:px-20 text-white text-center">
<h2 class="text-3xl md:text-4xl font-bold mb-4">Ready to impress recruiters and clients?</h2> <h2 class="text-3xl md:text-4xl font-bold mb-4">Ready to impress recruiters and clients?</h2>
<p class="mb-6 text-lg">Create your portfolio in minutes — no coding required.</p> <p class="mb-6 text-lg">Host your portfolio in seconds.</p>
<button class="bg-white text-blue-600 font-bold py-2 px-6 rounded-xl shadow hover:bg-gray-100"> <button class="bg-white text-blue-600 font-bold py-2 px-6 rounded-xl shadow hover:bg-gray-100">
Start My Portfolio Now Start My Portfolio Now
</button> </button>

View File

@ -1,6 +1,7 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {ApiService} from '../../services/api'; import {ApiService} from '../../services/api';
import {PortfolioService} from '../../services/portfolio.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -10,6 +11,7 @@ export class LandingComponent {
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private portfolioService: PortfolioService,
private router: Router, private router: Router,
) { ) {
} }
@ -18,4 +20,13 @@ export class LandingComponent {
this.router.navigate(['/register']); this.router.navigate(['/register']);
} }
exemple() {
this.portfolioService.getRandomPortfolio().subscribe({
next: (res) => {
window.location.href = 'https://' + res.data.host;
}
}
)
}
} }

View File

@ -1,4 +1,5 @@
export interface Portfolio { export interface Portfolio {
active: boolean;
id: number; id: number;
name: string; name: string;
domain: string; domain: string;

View File

@ -1,8 +1,9 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
import { Observable, throwError, BehaviorSubject } from 'rxjs'; import {BehaviorSubject, Observable, throwError} from 'rxjs';
import {catchError, tap} from 'rxjs/operators'; import {catchError, tap} from 'rxjs/operators';
import {environmentProd as env} from '../../environments/environment'; import {environmentProd as env} from '../../environments/environment';
import {Router} from '@angular/router';
export interface LoginCredentials { export interface LoginCredentials {
email: string; email: string;
@ -37,32 +38,7 @@ export class ApiService {
private tokenSubject = new BehaviorSubject<string | null>(this.getStoredToken()); private tokenSubject = new BehaviorSubject<string | null>(this.getStoredToken());
public token$ = this.tokenSubject.asObservable(); public token$ = this.tokenSubject.asObservable();
constructor(protected http: HttpClient) {} constructor(protected http: HttpClient, private router: Router) {}
// Get stored token from localStorage
private getStoredToken(): string | null {
if (typeof window !== 'undefined') {
return localStorage.getItem('auth_token');
}
return null;
}
// Set token in localStorage and update subject
private setToken(token: string): void {
if (typeof window !== 'undefined') {
localStorage.setItem('auth_token', token);
}
this.tokenSubject.next(token);
}
// Remove token from localStorage and update subject
private removeToken(): void {
if (typeof window !== 'undefined') {
console.log('?? ?? ??')
localStorage.removeItem('auth_token');
}
this.tokenSubject.next(null);
}
// Get current token value // Get current token value
get currentToken(): string | null { get currentToken(): string | null {
@ -74,79 +50,6 @@ export class ApiService {
return !!this.currentToken; return !!this.currentToken;
} }
// Get HTTP headers with auth token
private getHeaders(): HttpHeaders {
let headers = new HttpHeaders({
//'Content-Type': 'application/json',
//'Accept': 'application/json'
});
const token = this.currentToken;
if (token) {
headers = headers.set('Authorization', `Bearer ${token}`);
}
return headers;
}
// Handle HTTP errors
private handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = 'An unknown error occurred';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = error.error.message;
} else {
// Server-side error
if (error.status === 401) {
//this.removeToken();
errorMessage = 'Unauthorized. Please login again.';
} else if (error.status === 422) {
errorMessage = 'Validation error';
} else if (error.error?.message) {
errorMessage = error.error.message;
} else {
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
}
console.error('API Error:', error);
return throwError(() => ({ ...error, userMessage: errorMessage }));
}
// Generic HTTP methods
protected get<T>(endpoint: string): Observable<T> {
return this.http.get<T>(`${this.baseUrl}${endpoint}`, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
protected post<T>(endpoint: string, data: any): Observable<T> {
return this.http.post<T>(`${this.baseUrl}${endpoint}`, data, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
private put<T>(endpoint: string, data: any): Observable<T> {
return this.http.put<T>(`${this.baseUrl}${endpoint}`, data, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
private delete<T>(endpoint: string): Observable<T> {
return this.http.delete<T>(`${this.baseUrl}${endpoint}`, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
// Authentication methods // Authentication methods
register(userData: RegisterData): Observable<ApiResponse<AuthResponse>> { register(userData: RegisterData): Observable<ApiResponse<AuthResponse>> {
return this.post<ApiResponse<AuthResponse>>('/auth/register', userData).pipe( return this.post<ApiResponse<AuthResponse>>('/auth/register', userData).pipe(
@ -207,4 +110,102 @@ export class ApiService {
deleteData<T>(endpoint: string): Observable<T> { deleteData<T>(endpoint: string): Observable<T> {
return this.delete<T>(endpoint); return this.delete<T>(endpoint);
} }
// Generic HTTP methods
protected get<T>(endpoint: string): Observable<T> {
return this.http.get<T>(`${this.baseUrl}${endpoint}`, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
protected post<T>(endpoint: string, data: any): Observable<T> {
return this.http.post<T>(`${this.baseUrl}${endpoint}`, data, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
// Get stored token from localStorage
private getStoredToken(): string | null {
if (typeof window !== 'undefined') {
return localStorage.getItem('auth_token');
}
return null;
}
// Set token in localStorage and update subject
private setToken(token: string): void {
if (typeof window !== 'undefined') {
localStorage.setItem('auth_token', token);
}
this.tokenSubject.next(token);
}
// Remove token from localStorage and update subject
private removeToken(): void {
if (typeof window !== 'undefined') {
console.log('?? ?? ??')
localStorage.removeItem('auth_token');
}
this.tokenSubject.next(null);
}
// Get HTTP headers with auth token
private getHeaders(): HttpHeaders {
let headers = new HttpHeaders({
//'Content-Type': 'application/json',
//'Accept': 'application/json'
});
const token = this.currentToken;
if (token) {
headers = headers.set('Authorization', `Bearer ${token}`);
}
return headers;
}
// Handle HTTP errors
private handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = 'An unknown error occurred';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = error.error.message;
} else {
// Server-side error
if (error.status === 401) {
this.removeToken();
this.router.navigate(['/login'])
} else if (error.status === 422) {
errorMessage = 'Validation error';
} else if (error.error?.message) {
errorMessage = error.error.message;
} else {
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
}
console.error('API Error:', error);
return throwError(() => ({...error, userMessage: errorMessage}));
}
private put<T>(endpoint: string, data: any): Observable<T> {
return this.http.put<T>(`${this.baseUrl}${endpoint}`, data, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
private delete<T>(endpoint: string): Observable<T> {
return this.http.delete<T>(`${this.baseUrl}${endpoint}`, {
headers: this.getHeaders()
}).pipe(
catchError(this.handleError.bind(this))
);
}
} }

View File

@ -27,4 +27,9 @@ export class PortfolioService extends ApiService{
console.log('Deploying portfolio with ID:', portfolioId); console.log('Deploying portfolio with ID:', portfolioId);
return this.http.post<ApiResponse>(this.baseUrl + `/portfolios/${portfolioId}/deploy`, {}); return this.http.post<ApiResponse>(this.baseUrl + `/portfolios/${portfolioId}/deploy`, {});
} }
getRandomPortfolio() : Observable<ApiResponse>
{
return this.http.get<ApiResponse>(this.baseUrl + `/portfolio/random`, {});
}
} }