la base du site fonctionnelle
This commit is contained in:
committed by
styve Lioumba
parent
27d260829c
commit
1bf76c6c66
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
*.env
|
||||
Dockerfile
|
||||
package-lock.json
|
||||
16
.editorconfig
Normal file
16
.editorconfig
Normal 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
|
||||
67
.github/workflows/tests/docker-build.yaml
vendored
Normal file
67
.github/workflows/tests/docker-build.yaml
vendored
Normal 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
45
.gitignore
vendored
@@ -77,3 +77,48 @@ fabric.properties
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.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
4
.vscode/extensions.json
vendored
Normal 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
20
.vscode/launch.json
vendored
Normal 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
42
.vscode/tasks.json
vendored
Normal 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
33
Dockerfile
Normal 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
|
||||
118
angular.json
Normal file
118
angular.json
Normal 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
8
nginx.conf
Normal 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
12981
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
54
package.json
Normal file
54
package.json
Normal 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
56
server.ts
Normal 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();
|
||||
7
src/app/app.component.html
Normal file
7
src/app/app.component.html
Normal 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>
|
||||
18
src/app/app.component.scss
Normal file
18
src/app/app.component.scss
Normal 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 */
|
||||
}
|
||||
29
src/app/app.component.spec.ts
Normal file
29
src/app/app.component.spec.ts
Normal 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
16
src/app/app.component.ts
Normal 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';
|
||||
}
|
||||
11
src/app/app.config.server.ts
Normal file
11
src/app/app.config.server.ts
Normal 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
29
src/app/app.config.ts
Normal 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
26
src/app/app.routes.ts
Normal 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'}
|
||||
];
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
6
src/app/core/resolvers/profile/detail/detail.resolver.ts
Normal file
6
src/app/core/resolvers/profile/detail/detail.resolver.ts
Normal 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!";
|
||||
};
|
||||
17
src/app/core/resolvers/profile/list/list.resolver.spec.ts
Normal file
17
src/app/core/resolvers/profile/list/list.resolver.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
6
src/app/core/resolvers/profile/list/list.resolver.ts
Normal file
6
src/app/core/resolvers/profile/list/list.resolver.ts
Normal 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!";
|
||||
};
|
||||
13
src/app/routes/home/home-routing.module.ts
Normal file
13
src/app/routes/home/home-routing.module.ts
Normal 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 { }
|
||||
70
src/app/routes/home/home.component.html
Normal file
70
src/app/routes/home/home.component.html
Normal 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>
|
||||
|
||||
46
src/app/routes/home/home.component.scss
Normal file
46
src/app/routes/home/home.component.scss
Normal 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;}
|
||||
}
|
||||
23
src/app/routes/home/home.component.spec.ts
Normal file
23
src/app/routes/home/home.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
23
src/app/routes/home/home.component.ts
Normal file
23
src/app/routes/home/home.component.ts
Normal 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}});
|
||||
}
|
||||
}
|
||||
14
src/app/routes/home/home.module.ts
Normal file
14
src/app/routes/home/home.module.ts
Normal 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 { }
|
||||
13
src/app/routes/not-found/not-found-routing.module.ts
Normal file
13
src/app/routes/not-found/not-found-routing.module.ts
Normal 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 { }
|
||||
1
src/app/routes/not-found/not-found.component.html
Normal file
1
src/app/routes/not-found/not-found.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>not-found works!</p>
|
||||
0
src/app/routes/not-found/not-found.component.scss
Normal file
0
src/app/routes/not-found/not-found.component.scss
Normal file
23
src/app/routes/not-found/not-found.component.spec.ts
Normal file
23
src/app/routes/not-found/not-found.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
12
src/app/routes/not-found/not-found.component.ts
Normal file
12
src/app/routes/not-found/not-found.component.ts
Normal 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 {
|
||||
|
||||
}
|
||||
14
src/app/routes/not-found/not-found.module.ts
Normal file
14
src/app/routes/not-found/not-found.module.ts
Normal 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 { }
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
18
src/app/routes/profile/profile-routing.module.ts
Normal file
18
src/app/routes/profile/profile-routing.module.ts
Normal 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 {
|
||||
}
|
||||
14
src/app/routes/profile/profile.module.ts
Normal file
14
src/app/routes/profile/profile.module.ts
Normal 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 { }
|
||||
78
src/app/shared/components/footer/footer.component.html
Normal file
78
src/app/shared/components/footer/footer.component.html
Normal 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>
|
||||
23
src/app/shared/components/footer/footer.component.spec.ts
Normal file
23
src/app/shared/components/footer/footer.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
15
src/app/shared/components/footer/footer.component.ts
Normal file
15
src/app/shared/components/footer/footer.component.ts
Normal 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 {
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
67
src/app/shared/components/nav-bar/nav-bar.component.html
Normal file
67
src/app/shared/components/nav-bar/nav-bar.component.html
Normal 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>
|
||||
23
src/app/shared/components/nav-bar/nav-bar.component.spec.ts
Normal file
23
src/app/shared/components/nav-bar/nav-bar.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
15
src/app/shared/components/nav-bar/nav-bar.component.ts
Normal file
15
src/app/shared/components/nav-bar/nav-bar.component.ts
Normal 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 {
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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"])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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>();
|
||||
}
|
||||
35
src/app/shared/features/search/search.component.html
Normal file
35
src/app/shared/features/search/search.component.html
Normal 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>
|
||||
23
src/app/shared/features/search/search.component.spec.ts
Normal file
23
src/app/shared/features/search/search.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
34
src/app/shared/features/search/search.component.ts
Normal file
34
src/app/shared/features/search/search.component.ts
Normal 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
0
src/assets/.gitkeep
Normal file
BIN
src/assets/images/ttp.jpg
Normal file
BIN
src/assets/images/ttp.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
3
src/environments/environment.development.ts
Normal file
3
src/environments/environment.development.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
3
src/environments/environment.ts
Normal file
3
src/environments/environment.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
||||
BIN
src/favicon.ico
Normal file
BIN
src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
13
src/index.html
Normal file
13
src/index.html
Normal 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
7
src/main.server.ts
Normal 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
6
src/main.ts
Normal 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
9
src/styles.scss
Normal 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
11
tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{html,ts}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
18
tsconfig.app.json
Normal file
18
tsconfig.app.json
Normal 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
41
tsconfig.json
Normal 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
14
tsconfig.spec.json
Normal 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"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user