commit
300d55fe76
@ -1,4 +1,4 @@
|
|||||||
<app-navbar></app-navbar>
|
<app-navbar *ngIf="isAuth"></app-navbar>
|
||||||
<!-- <app-leaflet-map></app-leaflet-map> -->
|
<app-home-navbar *ngIf="!isAuth"></app-home-navbar>
|
||||||
|
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import { HomeNavbarComponent } from './components/home-navbar/home-navbar.component';
|
||||||
import { NavbarComponent } from './components/navbar/navbar.component';
|
import { NavbarComponent } from './components/navbar/navbar.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, NavbarComponent],
|
imports: [RouterOutlet, NavbarComponent, HomeNavbarComponent, CommonModule],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'frontv2';
|
title = 'Memory Map';
|
||||||
|
isAuth: boolean = false;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (localStorage.getItem('auth_token') !== null) {
|
||||||
|
this.isAuth = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes),provideHttpClient()]
|
providers: [
|
||||||
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
|
provideRouter(routes),
|
||||||
|
provideHttpClient(),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
<!-- Modal toggle -->
|
||||||
|
<button
|
||||||
|
data-modal-target="authentication-modal"
|
||||||
|
data-modal-toggle="authentication-modal"
|
||||||
|
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<p *ngIf="!isHomePage">Add a pin</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Main modal -->
|
||||||
|
<div
|
||||||
|
id="authentication-modal"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="hidden overflow-auto absolute top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full"
|
||||||
|
>
|
||||||
|
<div class="relative p-4 w-full max-w-xl max-h-full">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
|
<!-- Modal header -->
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600"
|
||||||
|
>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
|
Créer ton souvenir !
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||||
|
data-modal-hide="authentication-modal"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-3 h-3"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Close modal</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Modal body -->
|
||||||
|
<div class="p-4 md:p-5">
|
||||||
|
<form class="grid gap-6 mb-1 md:grid-cols-2" [formGroup]="form">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="title"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||||
|
>Titre</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="title"
|
||||||
|
name="title"
|
||||||
|
id="title"
|
||||||
|
formControlName="title"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||||
|
placeholder="Mont Saint-Michel"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="localisation"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||||
|
>Localisation</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="localisation"
|
||||||
|
formControlName="location"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||||
|
placeholder="Enter location"
|
||||||
|
(focus)="onFocus()"
|
||||||
|
(blur)="onBlur()"
|
||||||
|
/>
|
||||||
|
<ul
|
||||||
|
*ngIf="suggestions.length > 0 && inputFocused"
|
||||||
|
class="bg-white dark:bg-gray-700 border border-gray-300 mt-2 rounded shadow absolute z-10 mr-5 max-h-60 overflow-auto"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
*ngFor="let suggestion of suggestions"
|
||||||
|
(click)="selectSuggestion(suggestion)"
|
||||||
|
class="p-2 block mb-2 text-sm font-medium text-gray-900 dark:text-white hover:bg-gray-500 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ suggestion.display_name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<app-drag-drop
|
||||||
|
(filesSelected)="onFilesReceived($event)"
|
||||||
|
></app-drag-drop>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="description"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||||
|
>Description</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
rows="4"
|
||||||
|
formControlName="description"
|
||||||
|
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Describe your memory"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<button
|
||||||
|
type="reset"
|
||||||
|
data-modal-hide="authentication-modal"
|
||||||
|
class="w-full text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
|
||||||
|
>
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
(click)="submitForm()"
|
||||||
|
class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||||
|
>
|
||||||
|
Valider
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddPinPopupComponent } from './add-pin-popup.component';
|
||||||
|
|
||||||
|
describe('AddPinPopupComponent', () => {
|
||||||
|
let component: AddPinPopupComponent;
|
||||||
|
let fixture: ComponentFixture<AddPinPopupComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AddPinPopupComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AddPinPopupComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,109 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import {
|
||||||
|
FormBuilder,
|
||||||
|
FormControl,
|
||||||
|
FormGroup,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
} from '@angular/forms';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import {
|
||||||
|
catchError,
|
||||||
|
debounceTime,
|
||||||
|
distinctUntilChanged,
|
||||||
|
switchMap,
|
||||||
|
} from 'rxjs/operators';
|
||||||
|
import { AddPinService } from '../../services/add-pin.service';
|
||||||
|
import { AutocompleteService } from '../../services/auto-complete.service';
|
||||||
|
import { DragDropComponent } from '../drag-drop/drag-drop.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-pin-popup',
|
||||||
|
standalone: true,
|
||||||
|
imports: [ReactiveFormsModule, CommonModule, DragDropComponent],
|
||||||
|
templateUrl: './add-pin-popup.component.html',
|
||||||
|
})
|
||||||
|
export class AddPinPopupComponent implements OnInit {
|
||||||
|
form: FormGroup;
|
||||||
|
suggestions: any[] = [];
|
||||||
|
inputFocused: boolean = false;
|
||||||
|
@Input() isHomePage: boolean = false;
|
||||||
|
files: any[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private autocompleteService: AutocompleteService,
|
||||||
|
private addPinService: AddPinService
|
||||||
|
) {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
title: new FormControl(''),
|
||||||
|
description: new FormControl(''),
|
||||||
|
location: new FormControl(''),
|
||||||
|
files: new FormControl(null),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus(): void {
|
||||||
|
this.inputFocused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur(): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.inputFocused = false; // Désactiver le focus après un petit délai pour permettre un clic sur la liste
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.form
|
||||||
|
.get('location')
|
||||||
|
?.valueChanges.pipe(
|
||||||
|
debounceTime(300), // Attendre 300ms après la dernière frappe
|
||||||
|
distinctUntilChanged(), // Ignorer si la nouvelle valeur est la même que la précédente
|
||||||
|
switchMap((query) => {
|
||||||
|
const trimmedQuery = query.trim();
|
||||||
|
if (trimmedQuery.length > 2) {
|
||||||
|
return this.autocompleteService.getAddressSuggestions(trimmedQuery);
|
||||||
|
}
|
||||||
|
return of([]);
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
console.error('Error fetching suggestions:', error);
|
||||||
|
return of([]);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe((data) => {
|
||||||
|
this.suggestions = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectSuggestion(suggestion: any): void {
|
||||||
|
const locationControl = this.form.get('location');
|
||||||
|
if (locationControl instanceof FormControl) {
|
||||||
|
locationControl.setValue(suggestion.display_name);
|
||||||
|
}
|
||||||
|
this.suggestions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
onFilesReceived(files: FileList): void {
|
||||||
|
this.files = Array.from(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitForm(): void {
|
||||||
|
if (this.form.valid) {
|
||||||
|
this.files = this.files.map((file) => {
|
||||||
|
return file.name; //TODO: Mettre le hash du fichier
|
||||||
|
});
|
||||||
|
|
||||||
|
const pinData = {
|
||||||
|
...this.form.value,
|
||||||
|
files: this.files, // Ajoutez les fichiers au payload
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Form submitted with data:', pinData);
|
||||||
|
|
||||||
|
this.addPinService.addPin(pinData);
|
||||||
|
} else {
|
||||||
|
console.error('Le formulaire est invalide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
<div
|
||||||
|
class="flex flex-col items-center justify-center w-full h-36 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-gray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500"
|
||||||
|
(click)="fileInput.click()"
|
||||||
|
(drop)="onDrop($event)"
|
||||||
|
(dragover)="onDragOver($event)"
|
||||||
|
(dragleave)="onDragLeave($event)"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
||||||
|
<svg
|
||||||
|
class="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 20 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
<span class="font-semibold">Click to upload</span> or drag and drop
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400">PNG, JPG or GIF</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Input masqué -->
|
||||||
|
<input
|
||||||
|
#fileInput
|
||||||
|
class="hidden"
|
||||||
|
type="file"
|
||||||
|
formControlName="files"
|
||||||
|
multiple
|
||||||
|
(change)="onFilesSelected($event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Zone pour afficher les fichiers sélectionnés -->
|
||||||
|
<div
|
||||||
|
*ngIf="fileNames.length > 0"
|
||||||
|
class="mt-2 text-sm text-gray-500 dark:text-gray-400"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let fileName of fileNames">
|
||||||
|
{{ fileName }}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="end-2.5 text-gray-400 bg-transparent hover:text-gray-900 rounded-lg text-sm w-6 h-6 ms-auto inline-flex justify-center items-center dark:hover:text-white"
|
||||||
|
(click)="removeFile(fileName)"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-2 h-2"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Delete</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DragDropComponent } from './drag-drop.component';
|
||||||
|
|
||||||
|
describe('DragDropComponent', () => {
|
||||||
|
let component: DragDropComponent;
|
||||||
|
let fixture: ComponentFixture<DragDropComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DragDropComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DragDropComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,48 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-drag-drop',
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './drag-drop.component.html',
|
||||||
|
})
|
||||||
|
export class DragDropComponent {
|
||||||
|
fileNames: string[] = [];
|
||||||
|
@Output() filesSelected = new EventEmitter<FileList>();
|
||||||
|
|
||||||
|
onFilesSelected(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
if (input.files) {
|
||||||
|
this.updateFileNames(input.files);
|
||||||
|
this.filesSelected.emit(input.files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDrop(event: DragEvent): void {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.dataTransfer && event.dataTransfer.files) {
|
||||||
|
this.updateFileNames(event.dataTransfer.files);
|
||||||
|
this.filesSelected.emit(event.dataTransfer.files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragOver(event: DragEvent): void {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragLeave(event: DragEvent): void {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFileNames(files: FileList): void {
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
this.fileNames.push(files[i].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFile(fileName: string): void {
|
||||||
|
const index = this.fileNames.indexOf(fileName);
|
||||||
|
this.fileNames.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
||||||
|
<div
|
||||||
|
class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"
|
||||||
|
>
|
||||||
|
<a class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||||
|
<img src="./logo.png" class="h-10" alt="Memory Map Logo" />
|
||||||
|
<span
|
||||||
|
class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white"
|
||||||
|
>Memory Map</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
class="items-center justify-between hidden w-full lg:flex lg:w-auto lg:order-1"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="flex flex-col p-4 lg:p-0 mt-4 font-medium border border-gray-100 rounded-lg bg-gray-50 lg:space-x-8 rtl:space-x-reverse lg:flex-row lg:mt-0 lg:border-0 lg:bg-white dark:bg-gray-800 lg:dark:bg-gray-900 dark:border-gray-700"
|
||||||
|
>
|
||||||
|
<li class="flex items-center">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="block text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
|
||||||
|
aria-current="page"
|
||||||
|
>
|
||||||
|
<span class="space-x-2 py-2">
|
||||||
|
<app-login-page></app-login-page>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center space-x-2">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
|
||||||
|
>Inscription
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HomeNavbarComponent } from './home-navbar.component';
|
||||||
|
|
||||||
|
describe('HomeNavbarComponent', () => {
|
||||||
|
let component: HomeNavbarComponent;
|
||||||
|
let fixture: ComponentFixture<HomeNavbarComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [HomeNavbarComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(HomeNavbarComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { LoginPageComponent } from '../login-page/login-page.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home-navbar',
|
||||||
|
imports: [LoginPageComponent],
|
||||||
|
templateUrl: './home-navbar.component.html',
|
||||||
|
})
|
||||||
|
export class HomeNavbarComponent {
|
||||||
|
constructor() {}
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
<div class="map-container h-[calc(100vh_-_72px)]">
|
<div class="map-container h-[calc(100vh_-_72px)]">
|
||||||
<div id="map" class="h-full w-full"></div>
|
<div id="map" class="h-full w-full z-0"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,55 +1,128 @@
|
|||||||
<!-- Modal toggle -->
|
<!-- Modal toggle -->
|
||||||
<button data-modal-target="authentication-modal" data-modal-toggle="authentication-modal" class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
|
<button
|
||||||
type="button">
|
data-modal-target="authentication-modal"
|
||||||
Connexion
|
data-modal-toggle="authentication-modal"
|
||||||
|
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Connexion
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Main modal -->
|
<!-- Main modal -->
|
||||||
<div id="authentication-modal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full transition-opacity duration-300 ease-in-out">
|
<div
|
||||||
<div class="relative p-4 w-full max-w-md max-h-full">
|
id="authentication-modal"
|
||||||
<!-- Modal content -->
|
tabindex="-1"
|
||||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
aria-hidden="true"
|
||||||
<!-- Modal header -->
|
class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full transition-opacity duration-300 ease-in-out"
|
||||||
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
>
|
||||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<div class="relative p-4 w-full max-w-md max-h-full">
|
||||||
Sign in to our platform
|
<!-- Modal content -->
|
||||||
</h3>
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
<button type="button" id="close-login-modal" class="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-hide="authentication-modal">
|
<!-- Modal header -->
|
||||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
<div
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600"
|
||||||
</svg>
|
>
|
||||||
<span class="sr-only">Close modal</span>
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
</button>
|
Sign in to our platform
|
||||||
</div>
|
</h3>
|
||||||
<!-- Modal body -->
|
<button
|
||||||
<div class="p-4 md:p-5">
|
type="button"
|
||||||
<form [formGroup]="userForm" class="space-y-4">
|
id="close-login-modal"
|
||||||
<div>
|
class="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||||
<label for="login" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your login</label>
|
data-modal-hide="authentication-modal"
|
||||||
<input formControlName="login" type="login" name="login" id="login" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="user" required />
|
>
|
||||||
</div>
|
<svg
|
||||||
<div>
|
class="w-3 h-3"
|
||||||
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
|
aria-hidden="true"
|
||||||
<input formControlName="password" type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" required />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</div>
|
fill="none"
|
||||||
<div class="flex justify-between">
|
viewBox="0 0 14 14"
|
||||||
<div class="flex items-start">
|
>
|
||||||
<div class="flex items-center h-5">
|
<path
|
||||||
<input id="remember" type="checkbox" value="" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-600 dark:border-gray-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800"/>
|
stroke="currentColor"
|
||||||
</div>
|
stroke-linecap="round"
|
||||||
<label for="remember" class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">Remember me</label>
|
stroke-linejoin="round"
|
||||||
</div>
|
stroke-width="2"
|
||||||
<a href="#" class="text-sm text-blue-700 hover:underline dark:text-blue-500">Lost Password?</a>
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||||
</div>
|
/>
|
||||||
<div *ngIf="errorMessage" class="text-red-500 text-sm">
|
</svg>
|
||||||
{{ errorMessage }}
|
<span class="sr-only">Close modal</span>
|
||||||
</div>
|
</button>
|
||||||
<button (click)="login()" class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Login to your account</button>
|
</div>
|
||||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-300">
|
<!-- Modal body -->
|
||||||
Not registered? <a href="#" class="text-blue-700 hover:underline dark:text-blue-500">Create account</a>
|
<div class="p-4 md:p-5">
|
||||||
</div>
|
<form [formGroup]="userForm" class="space-y-4">
|
||||||
</form>
|
<div>
|
||||||
|
<label
|
||||||
|
for="login"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||||
|
>Your login</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
formControlName="login"
|
||||||
|
type="login"
|
||||||
|
name="login"
|
||||||
|
id="login"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||||
|
placeholder="user"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="password"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||||
|
>Your password</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
formControlName="password"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<div class="flex items-center h-5">
|
||||||
|
<input
|
||||||
|
id="remember"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-600 dark:border-gray-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<label
|
||||||
|
for="remember"
|
||||||
|
class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||||
|
>Remember me</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-sm text-blue-700 hover:underline dark:text-blue-500"
|
||||||
|
>Lost Password?</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="errorMessage" class="text-red-500 text-sm">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
(click)="login()"
|
||||||
|
class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||||
|
>
|
||||||
|
Login to your account
|
||||||
|
</button>
|
||||||
|
<div class="text-sm font-medium text-gray-500 dark:text-gray-300">
|
||||||
|
Not registered?
|
||||||
|
<a href="#" class="text-blue-700 hover:underline dark:text-blue-500"
|
||||||
|
>Create account</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddPinService } from './add-pin.service';
|
||||||
|
|
||||||
|
describe('AddPinService', () => {
|
||||||
|
let service: AddPinService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(AddPinService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,37 @@
|
|||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class AddPinService {
|
||||||
|
private apiURL = environment.apiURL;
|
||||||
|
private token = localStorage.getItem('token');
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
addPin(pin: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
location: string;
|
||||||
|
files: any[];
|
||||||
|
}) {
|
||||||
|
const url = `${this.apiURL}/pin/add`;
|
||||||
|
const headers = new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.token,
|
||||||
|
});
|
||||||
|
return this.http
|
||||||
|
.post<any>(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
title: pin.title,
|
||||||
|
description: pin.description,
|
||||||
|
location: pin.location,
|
||||||
|
files: pin.files,
|
||||||
|
},
|
||||||
|
{ headers }
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AutoCompleteService } from './auto-complete.service';
|
||||||
|
|
||||||
|
describe('AutoCompleteService', () => {
|
||||||
|
let service: AutoCompleteService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(AutoCompleteService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,23 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class AutocompleteService {
|
||||||
|
private apiUrl = 'https://nominatim.openstreetmap.org/search';
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
getAddressSuggestions(query: string): Observable<any> {
|
||||||
|
return this.http.get(this.apiUrl, {
|
||||||
|
params: {
|
||||||
|
q: query,
|
||||||
|
format: 'json',
|
||||||
|
addressdetails: '1',
|
||||||
|
limit: '5',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue