la base du site fonctionnelle

This commit is contained in:
styve Lioumba
2024-09-25 15:58:42 +02:00
committed by styve Lioumba
parent 27d260829c
commit 1bf76c6c66
92 changed files with 14820 additions and 1 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
*.env
Dockerfile
package-lock.json

16
.editorconfig Normal file
View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@@ -0,0 +1,67 @@
name: Docker Build Check
# Déclencheur pour chaque pull request
on:
pull_request:
branches:
- main
- dev
- feat/*
- fix/*
jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. Checkout du code source du dépôt
- name: Checkout code
uses: actions/checkout@v3
# 2. Configuration de QEMU pour le support multi-plateformes
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# 3. Configuration de Docker Buildx pour la construction multi-arch
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
# 4. Test de la construction du Dockerfile pour les architectures amd64 et arm64
- name: Build local container
uses: docker/build-push-action@v4
with:
tags: lastrea/trouvetonprofile:latest
push: false
platforms: linux/arm64
load: true
# 5. Vérification de la taille de l'image construite
- name: Check image size
run: docker images lastrea/trouvetonprofile:latest --format "{{.Size}}"
# 6. Scan de l'image Docker pour détecter les vulnérabilités
- name: Scan Image
uses: anchore/scan-action@v3
id: scan
with:
image: "lastrea/trouvetonprofile:latest"
fail-build: true
severity-cutoff: critical
output-format: sarif
- name: Upload Anchore Scan SARIF Report
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
# 7. Vérification si la construction et le scan ont réussi
- name: Build and Scan verification
if: ${{ success() }}
run: echo "Dockerfile built and scanned successfully!"
# 8. Envoi d'un message d'échec en cas d'erreur lors de la construction ou du scan
- name: Notify on failure
if: ${{ failure() }}
run: echo "The Dockerfile build or scan failed! Please review the changes."

45
.gitignore vendored
View File

@@ -77,3 +77,48 @@ fabric.properties
# Android studio 3.1+ serialized cache file # Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
=======
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
start.sh

4
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

33
Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
# ETAPE 1: Construire l'application
FROM node:20-alpine AS node-builder
ARG APP_NAME=technostrea
ARG ENVIRONMENT=production
ARG NG_VERSION=18
ENV APP_NAME=$APP_NAME
ENV ENVIRONMENT=$ENVIRONMENT
ENV NG_VERSION=$NG_VERSION
WORKDIR /app
COPY package*.json ./
RUN npm cache clean --force
RUN npm install -g @angular/cli@$NG_VERSION
COPY . .
RUN npm install --legacy-peer-deps
RUN ng build --configuration=$ENVIRONMENT --output-path=dist/
# ETAPE 2: Héberger l'application sur un serveur web nginx
FROM nginx:1.26.0-alpine as server
EXPOSE 80
WORKDIR /usr/share/nginx/html
RUN rm -rf /usr/share/nginx/html/*
COPY --from=node-builder /app/dist/browser /usr/share/nginx/html
COPY --from=node-builder /app/nginx.conf /etc/nginx/conf.d/default.conf

View File

@@ -1 +1 @@
# TrouveTonProfile # TrouveTonProfile

118
angular.json Normal file
View File

@@ -0,0 +1,118 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"TrouveTonProfile": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/trouve-ton-profile",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"node_modules/primeng/resources/themes/lara-light-blue/theme.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/primeicons/primeicons.css",
"src/styles.scss"
],
"scripts": [],
"server": "src/main.server.ts",
"prerender": true,
"ssr": {
"entry": "server.ts"
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "TrouveTonProfile:build:production"
},
"development": {
"buildTarget": "TrouveTonProfile:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "TrouveTonProfile:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": "dda3ec82-e13e-4042-ae63-71d138479518"
}
}

8
nginx.conf Normal file
View File

@@ -0,0 +1,8 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
}

12981
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

54
package.json Normal file
View File

@@ -0,0 +1,54 @@
{
"name": "trouve-ton-profile",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"serve:ssr:TrouveTonProfile": "node dist/trouve-ton-profile/server/server.mjs"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.0",
"@angular/common": "^17.0.0",
"@angular/compiler": "^17.0.0",
"@angular/core": "^17.0.0",
"@angular/forms": "^17.0.0",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"@angular/platform-server": "^17.0.0",
"@angular/router": "^17.0.0",
"@angular/ssr": "^17.0.1",
"@fortawesome/angular-fontawesome": "^0.14.1",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"express": "^4.18.2",
"primeicons": "^7.0.0",
"primeng": "^17.18.10",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.1",
"@angular/cli": "^17.0.1",
"@angular/compiler-cli": "^17.0.0",
"@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0",
"@types/node": "^18.18.0",
"autoprefixer": "^10.4.20",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.12",
"typescript": "~5.2.2"
}
}

56
server.ts Normal file
View File

@@ -0,0 +1,56 @@
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');
const commonEngine = new CommonEngine();
server.set('view engine', 'html');
server.set('views', browserDistFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(browserDistFolder, {
maxAge: '1y'
}));
// All regular routes use the Angular engine
server.get('*', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine
.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
run();

View File

@@ -0,0 +1,7 @@
<main class="bg-white dark:bg-gray-900">
<app-nav-bar/>
<section class="content bg-white dark:bg-gray-900">
<router-outlet></router-outlet>
</section>
<app-footer/>
</main>

View File

@@ -0,0 +1,18 @@
main {
display: flex;
flex-direction: column;
min-height: 100vh; /* Minimum 100% de la hauteur de la fenêtre */
}
#content {
flex-grow: 1; /* Cette zone grandit pour remplir l'espace restant */
padding: 20px;
}
app-nav-bar {
flex-shrink: 0; /* Le header ne doit pas rétrécir */
}
app-footer {
flex-shrink: 0; /* Le footer ne doit pas rétrécir */
}

View File

@@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'TrouveTonProfile' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('TrouveTonProfile');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, TrouveTonProfile');
});
});

16
src/app/app.component.ts Normal file
View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import {NavBarComponent} from "@app/shared/components/nav-bar/nav-bar.component";
import {FooterComponent} from "@app/shared/components/footer/footer.component";
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, NavBarComponent, FooterComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'TrouveTonProfile';
}

View File

@@ -0,0 +1,11 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering()
]
};
export const config = mergeApplicationConfig(appConfig, serverConfig);

29
src/app/app.config.ts Normal file
View File

@@ -0,0 +1,29 @@
import {ApplicationConfig} from '@angular/core';
import {
PreloadAllModules,
provideRouter,
withInMemoryScrolling,
withPreloading,
withViewTransitions
} from '@angular/router';
import {routes} from './app.routes';
import {provideHttpClient, withFetch} from "@angular/common/http";
import {provideAnimations} from "@angular/platform-browser/animations";
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withViewTransitions(),
withPreloading(PreloadAllModules),
withInMemoryScrolling(
{
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
}
)),
provideAnimations(),
provideHttpClient(withFetch())
]
};

26
src/app/app.routes.ts Normal file
View File

@@ -0,0 +1,26 @@
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
title: 'Accueil',
loadChildren: () => import('@app/routes/home/home.module').then(m => m.HomeModule)
},
{
path: 'home',
title: 'Accueil',
loadChildren: () => import('@app/routes/home/home.module').then(m => m.HomeModule)
},
{
path: 'profiles',
title: 'Liste des profiles',
loadChildren: () => import('@app/routes/profile/profile.module').then(m => m.ProfileModule)
},
{
path: 'not-found',
title: 'Page non trouvée',
loadChildren: () => import('@app/routes/not-found/not-found.module').then(m => m.NotFoundModule)
},
{path: '', redirectTo: '/', pathMatch: 'full'},
{path: '**', redirectTo: '/not-found'}
];

View File

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { ResolveFn } from '@angular/router';
import { detailResolver } from './detail.resolver';
describe('detailResolver', () => {
const executeResolver: ResolveFn<boolean> = (...resolverParameters) =>
TestBed.runInInjectionContext(() => detailResolver(...resolverParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeResolver).toBeTruthy();
});
});

View File

@@ -0,0 +1,6 @@
import { ResolveFn } from '@angular/router';
export const detailResolver: ResolveFn<string> = (route, state) => {
const paramValue = route.params['name'];
return "profile-list-resolver works!, paramValue: " + paramValue || "no query value found!";
};

View File

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { ResolveFn } from '@angular/router';
import { listResolver } from './list.resolver';
describe('listResolver', () => {
const executeResolver: ResolveFn<boolean> = (...resolverParameters) =>
TestBed.runInInjectionContext(() => listResolver(...resolverParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeResolver).toBeTruthy();
});
});

View File

@@ -0,0 +1,6 @@
import { ResolveFn } from '@angular/router';
export const listResolver: ResolveFn<string> = (route, state) => {
const queryValue = route.queryParams['search'];
return "profile-list-resolver works!, queryValue: " + queryValue || "no query value found!";
};

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {HomeComponent} from "@app/routes/home/home.component";
const routes: Routes = [
{path: '', component: HomeComponent, title: 'Accueil'}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }

View File

@@ -0,0 +1,70 @@
<section class="pb-10 relative">
<div className="absolute inset-0 bg-heroPatternLight dark:bg-heroPatternDark"></div>
<div class="relative overflow-hidden">
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10">
<div class="mt-5 max-w-2xl text-center mx-auto">
<h1
class="block font-bold text-gray-800 dark:text-white text-3xl md:text-5xl lg:text-6xl">
Dans quel secteur se cache votre prochaine
<span class="text-gray-800 dark:text-white">pépites?</span>
<div id=container>
<div id=flip>
<div><div>Les finances</div></div>
<div><div>La Santé</div></div>
<div><div>Les Etudes</div></div>
</div>
</div>
</h1>
</div>
<div class="mt-8 mx-auto max-w-3xl space-y-2">
<app-search (onSearchChange)="showNewQuery($event)" />
</div>
</div>
</div>
<div class="max-w-6xl mx-auto px-4">
<div
class="w-full relative bg-purple-200 rounded-xl px-2 pt-8 space-y-4 md:space-y-0">
<div
class="relative md:absolute inset-0 z-[1] bg-transparent md:px-10 order-0">
<div class="h-full grid place-content-center md:w-1/2 lg:w-2/5">
<div class="px-8 space-y-3 text-center md:text-left">
<h1
class="text-purple-950 text-4xl font-bold mb-4 text-center md:text-left">
Votre prochain profile en quelques clicks
</h1>
<p class="text-center md:text-left">
Le moyen le plus simple de trouver le/la candidat(e) qu'il vous faut.
</p>
<button
class="items-center flex gap-2 px-12 py-3 rounded-full text-white bg-purple-900 mx-auto md:m-0">
<span class="inline-block w-5 h-5">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-full h-full fill-current"
viewBox="0 -960 960 960">
<path
d="M480-80Q319-217 239.5-334.5T160-552q0-150 96.5-239T480-880q127 0 223.5 89T800-552l84-84 56 56-180 180-180-180 56-56 84 84q0-109-69.5-178.5T480-800q-101 0-170.5 69.5T240-552q0 71 59 162.5T480-186q20-18 37-35l34-34q-5-10-8-21.5t-3-23.5q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29q-8 0-14.5-1t-13.5-3q-29 30-61.5 61T480-80Z" />
</svg>
</span>
<span>Réchercher maintenant</span>
</button>
</div>
</div>
</div>
<img
class="w-full h-auto hidden md:inline-block"
src="https://www.tripadvisor.ca/img2/trips/home-gai-entry-dv.png"
alt="" />
<img
class="w-full h-auto block md:hidden"
src="https://www.tripadvisor.ca/img2/trips/home-gai-entry-mv.png"
alt="" />
</div>
</div>
</section>

View File

@@ -0,0 +1,46 @@
#container {
color:#999;
text-transform: uppercase;
font-size:36px;
font-weight:bold;
display:block;
}
#flip {
margin-top: 10px;
height:50px;
overflow:hidden;
}
#flip > div > div {
color:#fff;
padding:4px 12px;
height:45px;
margin-bottom:45px;
display:inline-block;
}
#flip div:first-child {
animation: show 10s linear infinite;
}
#flip div div {
background:#42c58a;
}
#flip div:first-child div {
background:#4ec7f3;
}
#flip div:last-child div {
background:#DC143C;
}
@keyframes show {
0% {margin-top:-270px;}
5% {margin-top:-180px;}
33% {margin-top:-180px;}
38% {margin-top:-90px;}
66% {margin-top:-90px;}
71% {margin-top:0px;}
99.99% {margin-top:0px;}
100% {margin-top:-270px;}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HomeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,23 @@
import {Component, inject} from '@angular/core';
import {FaIconComponent} from "@fortawesome/angular-fontawesome";
import {SearchComponent} from "@app/shared/features/search/search.component";
import {Router} from "@angular/router";
@Component({
selector: 'app-home',
standalone: true,
imports: [
FaIconComponent,
SearchComponent
],
templateUrl: './home.component.html',
styleUrl: './home.component.scss'
})
export class HomeComponent {
private readonly router = inject(Router)
showNewQuery(newQuery: string) {
this.router.navigate(['/profiles'], {queryParams: {search: newQuery}});
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HomeRoutingModule } from './home-routing.module';
@NgModule({
declarations: [],
imports: [
CommonModule,
HomeRoutingModule
]
})
export class HomeModule { }

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {NotFoundComponent} from "@app/routes/not-found/not-found.component";
const routes: Routes = [
{path: '', component: NotFoundComponent, title: 'Page non trouvée'}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class NotFoundRoutingModule { }

View File

@@ -0,0 +1 @@
<p>not-found works!</p>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NotFoundComponent } from './not-found.component';
describe('NotFoundComponent', () => {
let component: NotFoundComponent;
let fixture: ComponentFixture<NotFoundComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NotFoundComponent]
})
.compileComponents();
fixture = TestBed.createComponent(NotFoundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-not-found',
standalone: true,
imports: [],
templateUrl: './not-found.component.html',
styleUrl: './not-found.component.scss'
})
export class NotFoundComponent {
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NotFoundRoutingModule } from './not-found-routing.module';
@NgModule({
declarations: [],
imports: [
CommonModule,
NotFoundRoutingModule
]
})
export class NotFoundModule { }

View File

@@ -0,0 +1,32 @@
<section class="text-gray-600 body-font">
<div class="container px-5 py-24 mx-auto flex flex-col">
<div class="lg:w-4/6 mx-auto">
<div class="rounded-lg h-64 overflow-hidden">
<img alt="content" class="object-cover object-center h-full w-full" src="https://dummyimage.com/1200x500">
</div>
<div class="flex flex-col sm:flex-row mt-10">
<div class="sm:w-1/3 text-center sm:pr-8 sm:py-8">
<div class="w-20 h-20 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10" viewBox="0 0 24 24">
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
<div class="flex flex-col items-center text-center justify-center">
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg">Phoebe Caulfield</h2>
<div class="w-12 h-1 bg-indigo-500 rounded mt-2 mb-4"></div>
<p class="text-base">Raclette knausgaard hella meggs normcore williamsburg enamel pin sartorial venmo tbh hot chicken gentrify portland.</p>
</div>
</div>
<div class="sm:w-2/3 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left">
<p class="leading-relaxed text-lg mb-4">Meggings portland fingerstache lyft, post-ironic fixie man bun banh mi umami everyday carry hexagon locavore direct trade art party. Locavore small batch listicle gastropub farm-to-table lumbersexual salvia messenger bag. Coloring book flannel truffaut craft beer drinking vinegar sartorial, disrupt fashion axe normcore meh butcher. Portland 90's scenester vexillologist forage post-ironic asymmetrical, chartreuse disrupt butcher paleo intelligentsia pabst before they sold out four loko. 3 wolf moon brooklyn.</p>
<a class="text-indigo-500 inline-flex items-center">Learn More
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileDetailComponent } from './profile-detail.component';
describe('ProfileDetailComponent', () => {
let component: ProfileDetailComponent;
let fixture: ComponentFixture<ProfileDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProfileDetailComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ProfileDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,19 @@
import {Component, inject} from '@angular/core';
import {ActivatedRoute} from "@angular/router";
@Component({
selector: 'app-profile-detail',
standalone: true,
imports: [],
templateUrl: './profile-detail.component.html',
styleUrl: './profile-detail.component.scss'
})
export class ProfileDetailComponent {
private readonly route = inject(ActivatedRoute);
protected profile = this.route.snapshot.data['profile'];
constructor() {
console.log(this.profile)
}
}

View File

@@ -0,0 +1,30 @@
<section class="pb-10 relative">
<div class="relative overflow-hidden">
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10">
<div class="mt-8 mx-auto max-w-3xl space-y-2">
<app-search/>
</div>
</div>
</div>
<div class="max-w-6xl mx-auto px-4">
<app-display-profile-card (onDisplayChange)="showNewDisplay($event)"/>
@switch (display()){
@case ('list'.toUpperCase()){
<app-horizental-profile-list/>
}
@case ('grid'.toUpperCase()){
<app-vertical-profile-list/>
}
@default{
<app-vertical-profile-list/>
}
}
</div>
</section>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileListComponent } from './profile-list.component';
describe('ProfileListComponent', () => {
let component: ProfileListComponent;
let fixture: ComponentFixture<ProfileListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProfileListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ProfileListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,47 @@
import {Component, inject, signal} from '@angular/core';
import {ActivatedRoute} from "@angular/router";
import {SearchComponent} from "@app/shared/features/search/search.component";
import {
HorizentalProfileItemComponent
} from "@app/shared/components/horizental-profile-item/horizental-profile-item.component";
import {
VerticalProfileItemComponent
} from "@app/shared/components/vertical-profile-item/vertical-profile-item.component";
import {DisplayProfileCardComponent} from "@app/shared/features/display-profile-card/display-profile-card.component";
import {JsonPipe} from "@angular/common";
import {
HorizentalProfileListComponent
} from "@app/shared/components/horizental-profile-list/horizental-profile-list.component";
import {
VerticalProfileListComponent
} from "@app/shared/components/vertical-profile-list/vertical-profile-list.component";
@Component({
selector: 'app-profile-list',
standalone: true,
imports: [
SearchComponent,
HorizentalProfileItemComponent,
VerticalProfileItemComponent,
DisplayProfileCardComponent,
JsonPipe,
HorizentalProfileListComponent,
VerticalProfileListComponent
],
templateUrl: './profile-list.component.html',
styleUrl: './profile-list.component.scss'
})
export class ProfileListComponent {
private readonly route = inject(ActivatedRoute);
protected profiles = this.route.snapshot.data['profiles'];
protected display = signal<string>('grid'.toUpperCase());
constructor() {
console.log(this.profiles)
}
showNewDisplay($event: string) {
this.display.set($event.toUpperCase())
}
}

View File

@@ -0,0 +1,18 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ProfileListComponent} from "@app/routes/profile/profile-list/profile-list.component";
import {ProfileDetailComponent} from "@app/routes/profile/profile-detail/profile-detail.component";
import {listResolver} from "@app/core/resolvers/profile/list/list.resolver";
import {detailResolver} from "@app/core/resolvers/profile/detail/detail.resolver";
const routes: Routes = [
{path: '', component: ProfileListComponent, title: 'Liste des profiles', resolve: {profiles: listResolver}},
{path: ':name', component: ProfileDetailComponent, title: 'Detail du profile', resolve: {profile: detailResolver}},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProfileRoutingModule {
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProfileRoutingModule } from './profile-routing.module';
@NgModule({
declarations: [],
imports: [
CommonModule,
ProfileRoutingModule
]
})
export class ProfileModule { }

View File

@@ -0,0 +1,78 @@
<footer class=" py-4 w-full min-h-full bg-white dark:bg-gray-900 ">
<div class="max-w-[80rem] mx-auto space-y-6 px-2 md:px-4 lg:px-6 pt-4">
<div class="h-px w-full bg-gray-900/10 bg-gray-800 dark:bg-white"></div>
<div
class="flex flex-row justify-between items-center text-sm text-gray-600">
<a
[routerLink]="['/']"
class="inline-flex items-center gap-1">
<span class="inline-block text-xl text-gray-800 dark:text-white">TrouveTonProfile</span>
</a>
<ul class="flex items-center gap-4">
<li>
<a [routerLink]="['/conditions']" class="inline-block text-gray-800 dark:text-white">Conditions</a>
</li>
<li>
<a [routerLink]="['/terms']" class="inline-block text-gray-800 dark:text-white">Terms</a>
</li>
<li>
<a [routerLink]="['/politiques']" class="inline-block text-gray-800 dark:text-white">Politiques</a>
</li>
</ul>
<ul class="flex items-center gap-4">
<li>
<a
target="_blank"
href="#"
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-full h-full fill-current"
viewBox="0 0 448 512">
<path
d="M100.28 448H7.4V148.9h92.88zM53.79 108.1C24.09 108.1 0 83.5 0 53.8a53.79 53.79 0 0 1 107.58 0c0 29.7-24.1 54.3-53.79 54.3zM447.9 448h-92.68V302.4c0-34.7-.7-79.2-48.29-79.2-48.29 0-55.69 37.7-55.69 76.7V448h-92.78V148.9h89.08v40.8h1.3c12.4-23.5 42.69-48.3 87.88-48.3 94 0 111.28 61.9 111.28 142.3V448z"/>
</svg>
</a>
</li>
<li>
<a
target="_blank"
href="#"
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-full h-full fill-current"
viewBox="0 0 24 24">
<g
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<path d="M0 0h24v24H0z"/>
<path
fill="currentColor"
d="M18 2a1 1 0 0 1 .993.883L19 3v4a1 1 0 0 1-.883.993L18 8h-3v1h3a1 1 0 0 1 .991 1.131l-.02.112l-1 4a1 1 0 0 1-.858.75L17 15h-2v6a1 1 0 0 1-.883.993L14 22h-4a1 1 0 0 1-.993-.883L9 21v-6H7a1 1 0 0 1-.993-.883L6 14v-4a1 1 0 0 1 .883-.993L7 9h2V8a6 6 0 0 1 5.775-5.996L15 2z"/>
</g>
</svg>
</a>
</li>
<li>
<a
target="_blank"
href="#"
class="inline-block rounded-full h-8 w-8 p-2 border text-gray-800 dark:text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-full h-full fill-current"
viewBox="0 0 512 512">
<path
d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/>
</svg>
</a>
</li>
</ul>
</div>
</div>
</footer>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FooterComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import {RouterLink} from "@angular/router";
@Component({
selector: 'app-footer',
standalone: true,
imports: [
RouterLink
],
templateUrl: './footer.component.html',
styleUrl: './footer.component.scss'
})
export class FooterComponent {
}

View File

@@ -0,0 +1,34 @@
<div class="items-center bg-gray-50 rounded-lg shadow sm:flex dark:bg-gray-800 dark:border-gray-700 cursor-pointer" (click)="onShowDetail(user)">
<a href="#">
<img class="w-full rounded-lg sm:rounded-none sm:rounded-l-lg" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/avatars/bonnie-green.png" alt="Bonnie Avatar">
</a>
<div class="p-5">
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
<a href="#">Bonnie Green</a>
</h3>
<span class="text-gray-500 dark:text-gray-400">CEO & Web Developer</span>
<p class="mt-3 mb-4 font-light text-gray-500 dark:text-gray-400">Bonnie drives the technical strategy of the flowbite platform and brand.</p>
<ul class="flex space-x-4 sm:mt-0">
<li>
<a href="#" class="text-gray-500 hover:text-gray-900 dark:hover:text-white">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd" /></svg>
</a>
</li>
<li>
<a href="#" class="text-gray-500 hover:text-gray-900 dark:hover:text-white">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" /></svg>
</a>
</li>
<li>
<a href="#" class="text-gray-500 hover:text-gray-900 dark:hover:text-white">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" /></svg>
</a>
</li>
<li>
<a href="#" class="text-gray-500 hover:text-gray-900 dark:hover:text-white">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path fill-rule="evenodd" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c5.51 0 10-4.48 10-10S17.51 2 12 2zm6.605 4.61a8.502 8.502 0 011.93 5.314c-.281-.054-3.101-.629-5.943-.271-.065-.141-.12-.293-.184-.445a25.416 25.416 0 00-.564-1.236c3.145-1.28 4.577-3.124 4.761-3.362zM12 3.475c2.17 0 4.154.813 5.662 2.148-.152.216-1.443 1.941-4.48 3.08-1.399-2.57-2.95-4.675-3.189-5A8.687 8.687 0 0112 3.475zm-3.633.803a53.896 53.896 0 013.167 4.935c-3.992 1.063-7.517 1.04-7.896 1.04a8.581 8.581 0 014.729-5.975zM3.453 12.01v-.26c.37.01 4.512.065 8.775-1.215.25.477.477.965.694 1.453-.109.033-.228.065-.336.098-4.404 1.42-6.747 5.303-6.942 5.629a8.522 8.522 0 01-2.19-5.705zM12 20.547a8.482 8.482 0 01-5.239-1.8c.152-.315 1.888-3.656 6.703-5.337.022-.01.033-.01.054-.022a35.318 35.318 0 011.823 6.475 8.4 8.4 0 01-3.341.684zm4.761-1.465c-.086-.52-.542-3.015-1.659-6.084 2.679-.423 5.022.271 5.314.369a8.468 8.468 0 01-3.655 5.715z" clip-rule="evenodd" /></svg>
</a>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HorizentalProfileItemComponent } from './horizental-profile-item.component';
describe('HorizentalProfileItemComponent', () => {
let component: HorizentalProfileItemComponent;
let fixture: ComponentFixture<HorizentalProfileItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HorizentalProfileItemComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HorizentalProfileItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,19 @@
import {Component, inject, Input} from '@angular/core';
import {Router} from "@angular/router";
@Component({
selector: 'app-horizental-profile-item',
standalone: true,
imports: [],
templateUrl: './horizental-profile-item.component.html',
styleUrl: './horizental-profile-item.component.scss'
})
export class HorizentalProfileItemComponent {
@Input() user: any = {};
protected router = inject(Router)
onShowDetail(user: any) {
this.router.navigate(['/profiles', "user1"])
}
}

View File

@@ -0,0 +1,11 @@
<section class="bg-white dark:bg-gray-900">
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 lg:px-6 ">
<div class="grid gap-8 mb-6 lg:mb-16 md:grid-cols-2">
@for (n of [1, 2, 3, 4]; track n) {
<app-horizental-profile-item/>
}
</div>
</div>
</section>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HorizentalProfileListComponent } from './horizental-profile-list.component';
describe('HorizentalProfileListComponent', () => {
let component: HorizentalProfileListComponent;
let fixture: ComponentFixture<HorizentalProfileListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HorizentalProfileListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HorizentalProfileListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import {
HorizentalProfileItemComponent
} from "@app/shared/components/horizental-profile-item/horizental-profile-item.component";
@Component({
selector: 'app-horizental-profile-list',
standalone: true,
imports: [
HorizentalProfileItemComponent
],
templateUrl: './horizental-profile-list.component.html',
styleUrl: './horizental-profile-list.component.scss'
})
export class HorizentalProfileListComponent {
}

View File

@@ -0,0 +1,67 @@
<header class="bg-white dark:bg-gray-900">
<div class="w-full">
<nav
class="fixed w-full z-20 top-0 start-1/2 max-w-6xl -translate-x-1/2 bg-white dark:bg-gray-900">
<div
class="max-w-screen-xl flex flex-nowrap items-center justify-between mx-auto p-4">
<a [routerLink]="['/']"
class="flex items-center space-x-3 rtl:space-x-reverse">
<div class="flex flex-row items-center gap-1 mr-3">
<span class="text-xl select-none text-black dark:text-white">TrouveTonProfile</span>
</div>
</a>
<div class="flex md:order-2 space-x-1 items-center">
<button
type="button"
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1">
<span class="inline-block h-4 w-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-full h-full fill-current"
viewBox="0 -960 960 960">
<path
d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z" />
</svg>
</span>
</button>
<span class="text-black dark:text-white"> | </span>
<button
type="button"
class="text-black dark:text-white font-medium rounded-lg text-sm px-4 py-2 text-center flex items-center justify-center gap-1">
<span class="inline-block h-4 w-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-full h-full fill-current"
viewBox="0 -960 960 960">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm246-164q-59 0-99.5-40.5T340-580q0-59 40.5-99.5T480-720q59 0 99.5 40.5T620-580q0 59-40.5 99.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q53 0 100-15.5t86-44.5q-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160Zm0-360q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm0-60Zm0 360Z" />
</svg>
</span>
<span class="hidden sm:block text-black dark:text-white">Se connecter</span>
</button>
<button
data-collapse-toggle="navbar-sticky"
type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="navbar-sticky"
aria-expanded="false">
<span class="sr-only">Open main menu</span>
<svg
class="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 17 14">
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 1h15M1 7h15M1 13h15" />
</svg>
</button>
</div>
</div>
</nav>
</div>
</header>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavBarComponent } from './nav-bar.component';
describe('NavBarComponent', () => {
let component: NavBarComponent;
let fixture: ComponentFixture<NavBarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NavBarComponent]
})
.compileComponents();
fixture = TestBed.createComponent(NavBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import {RouterLink} from "@angular/router";
@Component({
selector: 'app-nav-bar',
standalone: true,
imports: [
RouterLink
],
templateUrl: './nav-bar.component.html',
styleUrl: './nav-bar.component.scss'
})
export class NavBarComponent {
}

View File

@@ -0,0 +1,29 @@
<div class="text-center text-gray-500 dark:text-gray-400 cursor-pointer" (click)="onShowDetail(user)">
<img class="mx-auto mb-4 w-36 h-36 rounded-full" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/avatars/bonnie-green.png" alt="Bonnie Avatar">
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
<a href="#">Bonnie Green</a>
</h3>
<p>CEO/Co-founder</p>
<ul class="flex justify-center mt-4 space-x-4">
<li>
<a href="#" class="text-[#39569c] hover:text-gray-900 dark:hover:text-white">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd" /></svg>
</a>
</li>
<li>
<a href="#" class="text-[#00acee] hover:text-gray-900 dark:hover:text-white">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" /></svg>
</a>
</li>
<li>
<a href="#" class="text-gray-900 hover:text-gray-900 dark:hover:text-white dark:text-gray-300">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" /></svg>
</a>
</li>
<li>
<a href="#" class="text-[#ea4c89] hover:text-gray-900 dark:hover:text-white">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path fill-rule="evenodd" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c5.51 0 10-4.48 10-10S17.51 2 12 2zm6.605 4.61a8.502 8.502 0 011.93 5.314c-.281-.054-3.101-.629-5.943-.271-.065-.141-.12-.293-.184-.445a25.416 25.416 0 00-.564-1.236c3.145-1.28 4.577-3.124 4.761-3.362zM12 3.475c2.17 0 4.154.813 5.662 2.148-.152.216-1.443 1.941-4.48 3.08-1.399-2.57-2.95-4.675-3.189-5A8.687 8.687 0 0112 3.475zm-3.633.803a53.896 53.896 0 013.167 4.935c-3.992 1.063-7.517 1.04-7.896 1.04a8.581 8.581 0 014.729-5.975zM3.453 12.01v-.26c.37.01 4.512.065 8.775-1.215.25.477.477.965.694 1.453-.109.033-.228.065-.336.098-4.404 1.42-6.747 5.303-6.942 5.629a8.522 8.522 0 01-2.19-5.705zM12 20.547a8.482 8.482 0 01-5.239-1.8c.152-.315 1.888-3.656 6.703-5.337.022-.01.033-.01.054-.022a35.318 35.318 0 011.823 6.475 8.4 8.4 0 01-3.341.684zm4.761-1.465c-.086-.52-.542-3.015-1.659-6.084 2.679-.423 5.022.271 5.314.369a8.468 8.468 0 01-3.655 5.715z" clip-rule="evenodd" /></svg>
</a>
</li>
</ul>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { VerticalProfileItemComponent } from './vertical-profile-item.component';
describe('VerticalProfileItemComponent', () => {
let component: VerticalProfileItemComponent;
let fixture: ComponentFixture<VerticalProfileItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VerticalProfileItemComponent]
})
.compileComponents();
fixture = TestBed.createComponent(VerticalProfileItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,20 @@
import {Component, inject, Input} from '@angular/core';
import {Router} from "@angular/router";
@Component({
selector: 'app-vertical-profile-item',
standalone: true,
imports: [],
templateUrl: './vertical-profile-item.component.html',
styleUrl: './vertical-profile-item.component.scss'
})
export class VerticalProfileItemComponent {
@Input() user: any = {};
protected router = inject(Router)
onShowDetail(user: any) {
this.router.navigate(['/profiles', "user1"])
}
}

View File

@@ -0,0 +1,11 @@
<section class="bg-white dark:bg-gray-900">
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 lg:px-6">
<div class="grid gap-8 lg:gap-16 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
@for (n of [1, 2, 3, 4]; track n) {
<app-vertical-profile-item/>
}
</div>
</div>
</section>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { VerticalProfileListComponent } from './vertical-profile-list.component';
describe('VerticalProfileListComponent', () => {
let component: VerticalProfileListComponent;
let fixture: ComponentFixture<VerticalProfileListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VerticalProfileListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(VerticalProfileListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import {
VerticalProfileItemComponent
} from "@app/shared/components/vertical-profile-item/vertical-profile-item.component";
@Component({
selector: 'app-vertical-profile-list',
standalone: true,
imports: [
VerticalProfileItemComponent
],
templateUrl: './vertical-profile-list.component.html',
styleUrl: './vertical-profile-list.component.scss'
})
export class VerticalProfileListComponent {
}

View File

@@ -0,0 +1,8 @@
<section class="w-full">
<div class="text-end">
<i class="pi pi-th-large text-gray-800 dark:text-white py-4 px-3 border rounded-l-lg cursor-pointer"
(click)="onDisplayChange.emit('grid'.toUpperCase())"></i>
<i class="pi pi-bars text-gray-800 dark:text-white py-4 px-3 border rounded-r-lg cursor-pointer"
(click)="onDisplayChange.emit('list'.toUpperCase())"></i>
</div>
</section>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DisplayProfileCardComponent } from './display-profile-card.component';
describe('DisplayProfileCardComponent', () => {
let component: DisplayProfileCardComponent;
let fixture: ComponentFixture<DisplayProfileCardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DisplayProfileCardComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DisplayProfileCardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,12 @@
import {Component, output} from '@angular/core';
@Component({
selector: 'app-display-profile-card',
standalone: true,
imports: [],
templateUrl: './display-profile-card.component.html',
styleUrl: './display-profile-card.component.scss'
})
export class DisplayProfileCardComponent {
onDisplayChange = output<string>();
}

View File

@@ -0,0 +1,35 @@
<form class="w-full relative" [formGroup]="searchForm" (ngSubmit)="onSubmit()">
<div class="flex w-full border rounded-full p-2 items-center">
<div class="flex-1 flex flex-row items-center">
<button class="inline-block w-8 h-8 p-1 text-gray-400">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-full h-full fill-current"
viewBox="0 0 512 512">
<path
d="M337.509 305.372h-17.501l-6.571-5.486c20.791-25.232 33.922-57.054 33.922-93.257C347.358 127.632 283.896 64 205.135 64 127.452 64 64 127.632 64 206.629s63.452 142.628 142.225 142.628c35.011 0 67.831-13.167 92.991-34.008l6.561 5.487v17.551L415.18 448 448 415.086 337.509 305.372zm-131.284 0c-54.702 0-98.463-43.887-98.463-98.743 0-54.858 43.761-98.742 98.463-98.742 54.7 0 98.462 43.884 98.462 98.742 0 54.856-43.762 98.743-98.462 98.743z"
fill="currentColor" />
</svg>
</button>
<input
formControlName="search"
type="search"
autocomplete="off"
autocorrect="off"
autocapitalize="none"
spellcheck="false"
required=""
class="flex-1 focus:ring-0 focus:outline-none placeholder:text-gray-400 bg-transparent text-gray-800 dark:text-white"
placeholder="Domaines, activités..."
title="Search"
role="searchbox"
aria-label="Search"
aria-controls="typeahead_results"
aria-autocomplete="list" />
</div>
<button
class="w-32 h-12 rounded-full bg-purple-800 hover:bg-purple-900 text-gray-50">
Trouver
</button>
</div>
</form>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchComponent } from './search.component';
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SearchComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,34 @@
import {Component, inject, output} from '@angular/core';
import {FormBuilder, FormControl, ReactiveFormsModule, Validators} from "@angular/forms";
@Component({
selector: 'app-search',
standalone: true,
imports: [
ReactiveFormsModule
],
templateUrl: './search.component.html',
styleUrl: './search.component.scss'
})
export class SearchComponent {
onSearchChange = output<string>()
private formBuilder: FormBuilder = inject(FormBuilder);
searchForm = this.formBuilder.group({
search: new FormControl('', Validators.required)
});
onSubmit(){
if (this.searchForm.invalid) {
return;
}
const search = this.searchForm.value.search?.toLowerCase()!;
this.setNewName(search);
}
setNewName(newName: string) {
this.onSearchChange.emit(newName);
}
}

0
src/assets/.gitkeep Normal file
View File

BIN
src/assets/images/ttp.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -0,0 +1,3 @@
export const environment = {
production: false
};

View File

@@ -0,0 +1,3 @@
export const environment = {
production: true
};

BIN
src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

13
src/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>TrouveTonProfile</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

7
src/main.server.ts Normal file
View File

@@ -0,0 +1,7 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';
const bootstrap = () => bootstrapApplication(AppComponent, config);
export default bootstrap;

6
src/main.ts Normal file
View File

@@ -0,0 +1,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

9
src/styles.scss Normal file
View File

@@ -0,0 +1,9 @@
/* You can add global styles to this file, and also import other style files */
@tailwind base;
@tailwind components;
@tailwind utilities;
html, body {
height: 100%;
margin: 0;
background: white;
}

11
tailwind.config.js Normal file
View File

@@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{html,ts}",
],
theme: {
extend: {},
},
plugins: [],
}

18
tsconfig.app.json Normal file
View File

@@ -0,0 +1,18 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": [
"node"
]
},
"files": [
"src/main.ts",
"src/main.server.ts",
"server.ts"
],
"include": [
"src/**/*.d.ts"
]
}

41
tsconfig.json Normal file
View File

@@ -0,0 +1,41 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": ".",
"resolveJsonModule": true,
"paths": {
"@app/core/*":["src/app/core/*"],
"@app/routes/*":["src/app/routes/*"],
"@app/shared/*":["src/app/shared/*"],
"@assets/*":["src/assets/*"],
"@env/*":["src/environments/*"],
},
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

14
tsconfig.spec.json Normal file
View File

@@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}