tag
This commit is contained in:
parent
d5c4a99281
commit
1fdf7516a5
@ -2,23 +2,22 @@ import { Routes } from '@angular/router';
|
|||||||
import {LoginComponent} from './components/login/login.component';
|
import {LoginComponent} from './components/login/login.component';
|
||||||
import {RegisterComponent} from './components/register/register.component';
|
import {RegisterComponent} from './components/register/register.component';
|
||||||
import {LandingComponent} from './components/landing/landing.component';
|
import {LandingComponent} from './components/landing/landing.component';
|
||||||
|
import {DashboardComponent} from './components/dashboard/dashboard.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login', component: LoginComponent, title: 'Login - Your App Name'
|
||||||
component: LoginComponent,
|
|
||||||
title: 'Login - Your App Name'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'register',
|
path: 'register', component: RegisterComponent, title: 'Register - Your App Name'
|
||||||
component: RegisterComponent,
|
|
||||||
title: 'Register - Your App Name'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '',
|
path: '', component: LandingComponent, title: 'Home'
|
||||||
component: LandingComponent,
|
},
|
||||||
title: 'Home'
|
|
||||||
}
|
{path: 'dashboard', component: DashboardComponent}
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
17
src/app/auth-interceptor.spec.ts
Normal file
17
src/app/auth-interceptor.spec.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { HttpInterceptorFn } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { authInterceptor } from './auth-interceptor';
|
||||||
|
|
||||||
|
describe('authInterceptor', () => {
|
||||||
|
const interceptor: HttpInterceptorFn = (req, next) =>
|
||||||
|
TestBed.runInInjectionContext(() => authInterceptor(req, next));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(interceptor).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
21
src/app/auth-interceptor.ts
Normal file
21
src/app/auth-interceptor.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {HttpEvent, HttpHandlerFn, HttpRequest} from '@angular/common/http';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
|
|
||||||
|
export function authInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
|
||||||
|
|
||||||
|
const token = localStorage.getItem('auth_token');
|
||||||
|
console.log('setting token ' + token);
|
||||||
|
if (token) {
|
||||||
|
const authReq = req.clone({
|
||||||
|
setHeaders: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next(authReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
96
src/app/components/dashboard/dashboard.component.html
Normal file
96
src/app/components/dashboard/dashboard.component.html
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<div class="max-w-7xl mx-auto p-6 space-y-10">
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800">📁 Your Portfolios</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Portfolios list -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
@for (p of portfolios; track p.id) {
|
||||||
|
<div class="border border-gray-200 rounded-2xl p-4 shadow-sm hover:shadow-md transition">
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-700 truncate">{{ p.name }}</h3>
|
||||||
|
<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'">
|
||||||
|
{{ p.path ? 'Uploaded' : 'Pending Upload' }}
|
||||||
|
</span>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row items-center justify-between gap-4 mt-4">
|
||||||
|
|
||||||
|
<!-- File Input -->
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
(change)="uploadZip($event, p.id)"
|
||||||
|
class="text-sm text-gray-500 file:mr-4 file:py-2 file:px-4
|
||||||
|
file:rounded-full file:border-0
|
||||||
|
file:text-sm file:font-semibold
|
||||||
|
file:bg-blue-50 file:text-blue-700
|
||||||
|
hover:file:bg-blue-100"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Deploy Button (conditional) -->
|
||||||
|
@if (p.path) {
|
||||||
|
<button
|
||||||
|
(click)="deploy(p.id)"
|
||||||
|
class="inline-flex items-center justify-center rounded-2xl px-3 py-2 text-sm font-medium
|
||||||
|
text-white bg-gradient-to-r from-blue-600 to-blue-500 shadow hover:shadow-md
|
||||||
|
transition-all duration-200 hover:from-blue-700 hover:to-blue-600"
|
||||||
|
>
|
||||||
|
🚀 Deploy
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Uploading Status -->
|
||||||
|
@if (uploading[p.id]) {
|
||||||
|
<p class="text-blue-500 text-sm mt-2 text-center w-full">Uploading...</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<hr class="border-gray-200"/>
|
||||||
|
|
||||||
|
<!-- Create Portfolio -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-800">➕ Create New Portfolio</h3>
|
||||||
|
|
||||||
|
<form (submit)="create(); $event.preventDefault()" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Name</label>
|
||||||
|
<input
|
||||||
|
[(ngModel)]="newPortfolio.name"
|
||||||
|
class="mt-3 p-2 block w-full rounded-xl border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Subdomain</label>
|
||||||
|
<input
|
||||||
|
[(ngModel)]="newPortfolio.domain"
|
||||||
|
class="mt-3 p-3 block w-full rounded-xl border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
name="domain"
|
||||||
|
placeholder="e.g. mysite.portfolio-host.com"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white font-semibold px-6 py-2 rounded-xl transition"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Create Portfolio
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
23
src/app/components/dashboard/dashboard.component.spec.ts
Normal file
23
src/app/components/dashboard/dashboard.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DashboardComponent } from './dashboard.component';
|
||||||
|
|
||||||
|
describe('DashboardComponent', () => {
|
||||||
|
let component: DashboardComponent;
|
||||||
|
let fixture: ComponentFixture<DashboardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DashboardComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DashboardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
58
src/app/components/dashboard/dashboard.component.ts
Normal file
58
src/app/components/dashboard/dashboard.component.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {PortfolioService} from '../../services/portfolio.service';
|
||||||
|
import {Portfolio} from '../../models/portfolio.model';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {NgClass} from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dashboard',
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
templateUrl: './dashboard.component.html'
|
||||||
|
})
|
||||||
|
export class DashboardComponent implements OnInit {
|
||||||
|
portfolios: Portfolio[] = [];
|
||||||
|
newPortfolio = { name: '', domain: '' };
|
||||||
|
uploading: { [key: number]: boolean } = {};
|
||||||
|
|
||||||
|
constructor(private portfolioService: PortfolioService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadPortfolios();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPortfolios() {
|
||||||
|
this.portfolioService.getPortfolios().subscribe({
|
||||||
|
next: (res) => this.portfolios = res.data || []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
this.portfolioService.createPortfolio(this.newPortfolio).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.newPortfolio = { name: '', domain: '' };
|
||||||
|
this.loadPortfolios();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadZip(event: Event, portfolioId: number) {
|
||||||
|
const file = (event.target as HTMLInputElement).files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
this.uploading[portfolioId] = true;
|
||||||
|
this.portfolioService.uploadZip(portfolioId, file).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.uploading[portfolioId] = false;
|
||||||
|
this.loadPortfolios();
|
||||||
|
},
|
||||||
|
error: () => this.uploading[portfolioId] = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy( portfolioId: number){
|
||||||
|
this.portfolioService.deploy(portfolioId).subscribe({});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import {Component, OnInit, OnDestroy} from '@angular/core';
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
import {Router, ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
import {Subject} from 'rxjs';
|
import {Subject} from 'rxjs';
|
||||||
import {takeUntil} from 'rxjs/operators';
|
import {takeUntil} from 'rxjs/operators';
|
||||||
import {ApiService, LoginCredentials} from '../../services/api';
|
import {ApiService, LoginCredentials} from '../../services/api';
|
||||||
@ -43,6 +43,11 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getter for easy access to form fields
|
||||||
|
get f() {
|
||||||
|
return this.loginForm.controls;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Get return url from route parameters or default to dashboard
|
// Get return url from route parameters or default to dashboard
|
||||||
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/dashboard';
|
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/dashboard';
|
||||||
@ -56,11 +61,6 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getter for easy access to form fields
|
|
||||||
get f() {
|
|
||||||
return this.loginForm.controls;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get specific field error message
|
// Get specific field error message
|
||||||
getFieldError(fieldName: string): string {
|
getFieldError(fieldName: string): string {
|
||||||
const field = this.loginForm.get(fieldName);
|
const field = this.loginForm.get(fieldName);
|
||||||
@ -84,11 +84,6 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
return !!(field?.errors && field.touched);
|
return !!(field?.errors && field.touched);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capitalize first letter
|
|
||||||
private capitalizeFirst(str: string): string {
|
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle password visibility
|
// Toggle password visibility
|
||||||
togglePasswordVisibility(): void {
|
togglePasswordVisibility(): void {
|
||||||
this.showPassword = !this.showPassword;
|
this.showPassword = !this.showPassword;
|
||||||
@ -126,6 +121,17 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
this.success = 'Login successful! Redirecting...';
|
this.success = 'Login successful! Redirecting...';
|
||||||
|
console.log(response.data?.token);
|
||||||
|
localStorage.setItem('auth_token_', 'TEST_TOKEN');
|
||||||
|
console.log(localStorage.getItem('auth_token_')); // Should be 'TEST_TOKEN'
|
||||||
|
localStorage.setItem('auth_token', response.data?.token || '');
|
||||||
|
|
||||||
|
|
||||||
|
console.log('Saving token:', response.data?.token);
|
||||||
|
console.log('Current origin:', window.location.origin);
|
||||||
|
localStorage.setItem('auth_token', response.data?.token || '');
|
||||||
|
console.log('Token in localStorage:', localStorage.getItem('auth_token'));
|
||||||
|
|
||||||
|
|
||||||
// Store remember me preference if needed
|
// Store remember me preference if needed
|
||||||
if (this.f['rememberMe'].value) {
|
if (this.f['rememberMe'].value) {
|
||||||
@ -187,4 +193,9 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
password: 'password123'
|
password: 'password123'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capitalize first letter
|
||||||
|
private capitalizeFirst(str: string): string {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,6 +137,7 @@ export class RegisterComponent implements OnInit, OnDestroy {
|
|||||||
this.router.navigate([this.returnUrl]);
|
this.router.navigate([this.returnUrl]);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
|
console.log(response);
|
||||||
this.error = response.message || 'Registration failed. Please try again.';
|
this.error = response.message || 'Registration failed. Please try again.';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
8
src/app/models/portfolio.model.ts
Normal file
8
src/app/models/portfolio.model.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface Portfolio {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
domain: string;
|
||||||
|
path?: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||||
import { Observable, throwError, BehaviorSubject } from 'rxjs';
|
import { Observable, throwError, BehaviorSubject } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import { environment as env } from '../../environments/environment' ;
|
import { environmentProd as env } from '../../environments/environment' ;
|
||||||
|
|
||||||
export interface LoginCredentials {
|
export interface LoginCredentials {
|
||||||
email: string;
|
email: string;
|
||||||
@ -33,11 +33,11 @@ export interface AuthResponse {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
private baseUrl = env.apiUrl || 'http://localhost:8000/api';
|
protected baseUrl = env.apiUrl || 'http://localhost:8000/api';
|
||||||
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(private http: HttpClient) {}
|
constructor(protected http: HttpClient) {}
|
||||||
|
|
||||||
// Get stored token from localStorage
|
// Get stored token from localStorage
|
||||||
private getStoredToken(): string | null {
|
private getStoredToken(): string | null {
|
||||||
@ -58,6 +58,7 @@ export class ApiService {
|
|||||||
// Remove token from localStorage and update subject
|
// Remove token from localStorage and update subject
|
||||||
private removeToken(): void {
|
private removeToken(): void {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
console.log('?? ?? ??')
|
||||||
localStorage.removeItem('auth_token');
|
localStorage.removeItem('auth_token');
|
||||||
}
|
}
|
||||||
this.tokenSubject.next(null);
|
this.tokenSubject.next(null);
|
||||||
@ -76,8 +77,8 @@ export class ApiService {
|
|||||||
// Get HTTP headers with auth token
|
// Get HTTP headers with auth token
|
||||||
private getHeaders(): HttpHeaders {
|
private getHeaders(): HttpHeaders {
|
||||||
let headers = new HttpHeaders({
|
let headers = new HttpHeaders({
|
||||||
'Content-Type': 'application/json',
|
//'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
//'Accept': 'application/json'
|
||||||
});
|
});
|
||||||
|
|
||||||
const token = this.currentToken;
|
const token = this.currentToken;
|
||||||
@ -98,7 +99,7 @@ export class ApiService {
|
|||||||
} else {
|
} else {
|
||||||
// Server-side error
|
// Server-side error
|
||||||
if (error.status === 401) {
|
if (error.status === 401) {
|
||||||
this.removeToken();
|
//this.removeToken();
|
||||||
errorMessage = 'Unauthorized. Please login again.';
|
errorMessage = 'Unauthorized. Please login again.';
|
||||||
} else if (error.status === 422) {
|
} else if (error.status === 422) {
|
||||||
errorMessage = 'Validation error';
|
errorMessage = 'Validation error';
|
||||||
@ -114,7 +115,7 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generic HTTP methods
|
// Generic HTTP methods
|
||||||
private get<T>(endpoint: string): Observable<T> {
|
protected get<T>(endpoint: string): Observable<T> {
|
||||||
return this.http.get<T>(`${this.baseUrl}${endpoint}`, {
|
return this.http.get<T>(`${this.baseUrl}${endpoint}`, {
|
||||||
headers: this.getHeaders()
|
headers: this.getHeaders()
|
||||||
}).pipe(
|
}).pipe(
|
||||||
@ -122,7 +123,7 @@ export class ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private post<T>(endpoint: string, data: any): Observable<T> {
|
protected post<T>(endpoint: string, data: any): Observable<T> {
|
||||||
return this.http.post<T>(`${this.baseUrl}${endpoint}`, data, {
|
return this.http.post<T>(`${this.baseUrl}${endpoint}`, data, {
|
||||||
headers: this.getHeaders()
|
headers: this.getHeaders()
|
||||||
}).pipe(
|
}).pipe(
|
||||||
@ -150,6 +151,7 @@ export class ApiService {
|
|||||||
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(
|
||||||
tap(response => {
|
tap(response => {
|
||||||
|
console.log(response)
|
||||||
if (response.success && response.data?.token) {
|
if (response.success && response.data?.token) {
|
||||||
this.setToken(response.data.token);
|
this.setToken(response.data.token);
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/app/services/portfolio.service.spec.ts
Normal file
16
src/app/services/portfolio.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PortfolioService } from './portfolio.service';
|
||||||
|
|
||||||
|
describe('PortfolioService', () => {
|
||||||
|
let service: PortfolioService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(PortfolioService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
30
src/app/services/portfolio.service.ts
Normal file
30
src/app/services/portfolio.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {ApiResponse, ApiService} from './api';
|
||||||
|
import {HttpClient} from '@angular/common/http';
|
||||||
|
import {Portfolio} from '../models/portfolio.model';
|
||||||
|
import { environmentProd as env } from '../../environments/environment' ;
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class PortfolioService extends ApiService{
|
||||||
|
|
||||||
|
|
||||||
|
getPortfolios(): Observable<ApiResponse<Portfolio[]>> {
|
||||||
|
return this.get<ApiResponse<Portfolio[]>>(`/portfolios`);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPortfolio(data: { name: string; domain: string }): Observable<ApiResponse<Portfolio>> {
|
||||||
|
return this.post<ApiResponse<Portfolio>>(`/portfolios`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadZip(portfolioId: number, file: File): Observable<ApiResponse> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
return this.http.post<ApiResponse>(this.baseUrl + `/portfolios/${portfolioId}/upload`, formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy(portfolioId: number): Observable<ApiResponse> {
|
||||||
|
console.log('Deploying portfolio with ID:', portfolioId);
|
||||||
|
return this.http.post<ApiResponse>(this.baseUrl + `/portfolios/${portfolioId}/deploy`, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/environments/environment.prod.ts
Normal file
4
src/environments/environment.prod.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const environmentProd = {
|
||||||
|
production: true,
|
||||||
|
apiUrl: 'https://api.portfolio-host.com/api'
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
export const environment = {
|
export const environmentProd = {
|
||||||
production: true,
|
production: true,
|
||||||
apiUrl: 'https://api.portfolio-host.com/api'
|
apiUrl: 'http://localhost:8000/api'
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
import {bootstrapApplication} from '@angular/platform-browser';
|
import {bootstrapApplication} from '@angular/platform-browser';
|
||||||
import {provideRouter} from '@angular/router';
|
import {provideRouter} from '@angular/router';
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
import {provideHttpClient, withInterceptors} from '@angular/common/http';
|
||||||
import {importProvidersFrom} from '@angular/core';
|
import {importProvidersFrom} from '@angular/core';
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
import {ReactiveFormsModule} from '@angular/forms';
|
||||||
|
|
||||||
import {App} from './app/app';
|
import {App} from './app/app';
|
||||||
import {routes} from './app/app.routes';
|
import {routes} from './app/app.routes';
|
||||||
|
import {authInterceptor} from './app/auth-interceptor';
|
||||||
|
|
||||||
bootstrapApplication(App, {
|
bootstrapApplication(App, {
|
||||||
providers: [
|
providers: [
|
||||||
provideRouter(routes),
|
provideRouter(routes),
|
||||||
provideHttpClient(),
|
provideHttpClient(withInterceptors([
|
||||||
importProvidersFrom(ReactiveFormsModule)
|
authInterceptor
|
||||||
|
])),
|
||||||
|
importProvidersFrom(ReactiveFormsModule),
|
||||||
]
|
]
|
||||||
}).catch(err => console.error(err));
|
}).catch(err => console.error(err));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user