diff --git a/proxy.conf.json b/proxy.conf.json index 9b204178d72eec14aeab3261a212a899fde93750..c94ca87f89b82f495d532cf91887df34a02e6024 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -1,6 +1,8 @@ { - "/api": { - "target": "http://localhost:3000", - "secure": false - } + "/api/*": { + "target": "http://localhost:3000", + "secure": false, + "logLevel": "debug", + "changeOrigin": true + } } \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b83e94dc99c511c3367d363678aeb8e051ba93d3..12c7e255986f949e4228d227e1039e1a0f10d232 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -18,19 +18,21 @@ import { APP_ROUTING } from './app.routing'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; + +// others import { environment } from 'src/environments/environment'; import { reducersMap } from './share/store'; import { AuthEffects } from './share/store/effects/auth.effects'; -import { WelcomeComponent } from './components/welcome/welcome.component'; +import { BarEffects } from './share/store/effects/bar.effects'; @NgModule({ - declarations: [AppComponent, WelcomeComponent], + declarations: [AppComponent], imports: [ BrowserModule, BrowserAnimationsModule, RouterModule.forRoot(APP_ROUTING), StoreModule.forRoot(reducersMap), - EffectsModule.forRoot([AuthEffects]), + EffectsModule.forRoot([AuthEffects, BarEffects]), StoreDevtoolsModule.instrument({ name: 'Ngrx', logOnly: environment.production diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index c766de6636eb14a932c64b8d85b706cff09c15af..c19269af79029ea223475624fa8b5bd95d2058fb 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -1,4 +1,5 @@ import { Route } from "@angular/router"; +import { BarsComponent } from "./components/bars/bars.component"; import { SigninComponent } from "./components/signin/signin.component"; import { SignupComponent } from "./components/signup/signup.component"; import { WelcomeComponent } from "./components/welcome/welcome.component"; @@ -9,9 +10,9 @@ export const APP_ROUTING: Route[] = [ { path: 'welcome', canActivate: [AlreadyLoggedInGuard], component: WelcomeComponent }, { path: 'photos', canActivate: [AuthGuard], loadChildren: () => import('./photos/photos.module').then(m => m.PhotosModule) }, { path: 'profile', canActivate: [AuthGuard], loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule) }, - // { path: 'bars', canActivate: [AuthGuard], loadChildren: () => import('./bars/bars.module').then(m => m.BarsModule) }, + { path: 'bars', canActivate: [AuthGuard], component: BarsComponent }, { path: 'signup', canActivate: [AlreadyLoggedInGuard], component: SignupComponent }, { path: 'signin', canActivate: [AlreadyLoggedInGuard], component: SigninComponent }, - { path: '', redirectTo: 'welcome', pathMatch: 'full' }, - { path: '**', redirectTo: 'welcome', pathMatch: 'full' }, + { path: '', redirectTo: '', pathMatch: 'full' }, + { path: '**', redirectTo: '', pathMatch: 'full' }, ]; \ No newline at end of file diff --git a/src/app/components/bars/bars.component.css b/src/app/components/bars/bars.component.css new file mode 100644 index 0000000000000000000000000000000000000000..6909c8df093464062508c70f3ae782a70aa66d7d --- /dev/null +++ b/src/app/components/bars/bars.component.css @@ -0,0 +1,69 @@ +.logo-img { + height: 60px; + width: 60px; +} + +.row-container { + display: flex; + flex-flow: row nowrap; + padding: 2px; +} + +.center { + justify-content: center; + align-items: center; +} + +.card { + background-color: #FFFFFF; + border-radius: 4px; + margin: 8px 15px; + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.12), 0px 1px 1px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(34, 34, 34, 0.2); +} + +.card-header, .card-actions { + background-color: inherit; + border: 0; + flex-grow: 0; + margin: 0; + padding: 0; +} + +.card-actions { + flex-grow: 1; +} + +.card-content { + flex-grow: 100; + text-align: start; +} + +.card-content > p { + font-size: 0.9em; + font-weight: bold; + color: #6E2424; +} + + +.card-content > div { + font-size: 0.8em; +} + +.card-action { + flex-grow: 1; +} + +/* TOP NOTE */ +.top-note { + margin: 40px 15px; +} + +.top-note > mat-icon { + weight: 50px; + height: 50px; + font-size: 50px; +} + +.top-note > p { + font-weight: 500; +} \ No newline at end of file diff --git a/src/app/components/bars/bars.component.html b/src/app/components/bars/bars.component.html new file mode 100644 index 0000000000000000000000000000000000000000..01a41e2bd17a8dcab408ab3b5c07a760ab4256d7 --- /dev/null +++ b/src/app/components/bars/bars.component.html @@ -0,0 +1,33 @@ +<div class="row-container center top-note"> + <mat-icon>store_mall_directory</mat-icon> + <p>Choisis ton lieu de beuverie. + Tu pourras ensuite accéder à la carte des boissons.</p> + </div> +<section> + <div *ngFor="let bar of (bars$ | async)" class=" card row-container center"> + <div class="card-header"> + <img class="logo-img" src="https://fakeimg.pl/300/" alt="fake-img"> + </div> + <div class="card-content"> + <p>{{ bar.longname }}</p> + <div>{{ bar.address }}</div> + </div> + <div class="card-action"> + <button mat-raised-button color='accent'>choisir</button> + </div> + </div> +</section> +<!-- <section> + <div class=" card row-container center"> + <div class="card-header"> + <img class="logo-img" src="https://fakeimg.pl/300/" alt="fake-img"> + </div> + <div class="card-content"> + <p>Déliriuem Café clermont-ferrand</p> + <div>{{ bar.address }}</div> + </div> + <div class="card-action"> + <button mat-raised-button color='accent'>choisir</button> + </div> + </div> +</section> --> diff --git a/src/app/components/bars/bars.component.ts b/src/app/components/bars/bars.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f2c600af103c5aeca634ba7cab5ccc763632d47 --- /dev/null +++ b/src/app/components/bars/bars.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Bar } from '../../share/models/bar.model'; +import { Store } from '@ngrx/store'; +import { State } from '../../share/store'; +import { allBarsSelector } from '../../share/store/selectors/bar.selectors'; +import { BarActionsTypes, FetchBars, FetchBarsSuccess } from 'src/app/share/store/actions/bar.actions'; +import { BarService } from 'src/app/share/services/bar.service'; + +@Component({ + selector: 'app-bars', + templateUrl: './bars.component.html', + styleUrls: ['./bars.component.css'] +}) +export class BarsComponent implements OnInit { + public bars$: Observable<Bar[]> = this.store.select(allBarsSelector); + + constructor( + private store: Store<State>, + ) {} + + ngOnInit(): void { + this.store.dispatch(new FetchBars()); + } +} diff --git a/src/app/photos/photos.component.ts b/src/app/photos/photos.component.ts index 09dcdc27553d300536cae54a1ef7a7e96785d200..501cb9c0058e8e52303ad9aeefed26ebf3082c97 100644 --- a/src/app/photos/photos.component.ts +++ b/src/app/photos/photos.component.ts @@ -22,8 +22,6 @@ export class PhotosComponent implements OnInit { private notificationService: NotificationService ) { this.swUpdate.available.subscribe((version) => { - console.log(version); - if (version) { this.swUpdate.activateUpdate().then(() => { window.location.reload(); diff --git a/src/app/photos/shared/store/photos.effects.ts b/src/app/photos/shared/store/photos.effects.ts index 551a9b714fc3927ac2b8c0dadbadd5d34c5ef8cb..6f3bd9740e2810bbc78f7d971cf3d21099abc192 100644 --- a/src/app/photos/shared/store/photos.effects.ts +++ b/src/app/photos/shared/store/photos.effects.ts @@ -1,7 +1,7 @@ import { Injectable } from "@angular/core"; import { Actions, Effect, ofType } from "@ngrx/effects"; import { select, Store } from "@ngrx/store"; -import { debounceTime, map, switchMap, take } from "rxjs/operators"; +import { debounceTime, map, switchMap, take, tap } from "rxjs/operators"; import { State } from "../../../share/store"; import { Photo } from "../models/photo.model"; diff --git a/src/app/photos/shared/store/photos.reducers.ts b/src/app/photos/shared/store/photos.reducers.ts index 629382d6d29a709d21082a71cef3e2b236d7bd07..b775b55c209801945ecc65cd7db1f3e3b38cdf57 100644 --- a/src/app/photos/shared/store/photos.reducers.ts +++ b/src/app/photos/shared/store/photos.reducers.ts @@ -3,7 +3,7 @@ import { Photo } from "../models/photo.model"; export interface PhotosState { photos: Photo[]; - filter:string; + filter: string; } const defaultPhotoState = { diff --git a/src/app/photos/shared/store/photos.selectors.ts b/src/app/photos/shared/store/photos.selectors.ts index d711f16c6aabcb8cad97e897130a96a26ae3f8a1..c68d8b490c4e1457152d7813bd973ca0272f9bf9 100644 --- a/src/app/photos/shared/store/photos.selectors.ts +++ b/src/app/photos/shared/store/photos.selectors.ts @@ -2,5 +2,7 @@ import { createFeatureSelector, createSelector } from "@ngrx/store"; import { PhotosState } from "./photos.reducers"; export const baseSelector = createFeatureSelector('photos'); + export const filterPhotosSelector = createSelector(baseSelector, (photosState: PhotosState) => photosState?.filter); + export const photosSelector = createSelector(baseSelector, (photosState: PhotosState) => photosState?.photos); \ No newline at end of file diff --git a/src/app/profile/profile.component.html b/src/app/profile/profile.component.html index cdfad31ac2e59bc8a0d47f1471d1f845896c528a..6ea1bd3a8b3939984ff94650b0c124703ffb8e6f 100644 --- a/src/app/profile/profile.component.html +++ b/src/app/profile/profile.component.html @@ -1 +1,8 @@ -<p>profile works!</p> \ No newline at end of file +<p>profile works!</p> +<p *ngIf="(currentUser$ | async) as user"> + {{ user.email }} - {{ user.pseudo }} +</p> +<p> + <button mat-raised-button routerLink="/bars" color="primary">Liste des bars</button> + <button mat-raised-button routerLink="/photos" color="accent">photos</button> +</p> \ No newline at end of file diff --git a/src/app/profile/profile.component.ts b/src/app/profile/profile.component.ts index daad8f9137f5bb108aacd4cb390fb9b2eacf4153..0a12a2d8c884b34beb1d4dda4ef26bc62a755389 100644 --- a/src/app/profile/profile.component.ts +++ b/src/app/profile/profile.component.ts @@ -19,7 +19,6 @@ export class ProfileComponent implements OnInit { ngOnInit(): void { this.currentUser$ = this.store.pipe(select(userAuthSelector)); this.store.dispatch(new TryFetchCurrentUser()); - console.log('finishing init'); } } diff --git a/src/app/share/components/topbar/topbar.component.html b/src/app/share/components/topbar/topbar.component.html index 72b04a4b108ac9bd2896e278d6d5ca22d84f29dd..8ab70b509498c7790a2c7a9dca3f1f90fb88956c 100644 --- a/src/app/share/components/topbar/topbar.component.html +++ b/src/app/share/components/topbar/topbar.component.html @@ -1,5 +1,5 @@ <mat-toolbar *ngIf="isLoggedIn$ | async" color="primary" fxLayoutGap="15px"> - <span routerLink="/">Mon logo</span> + <span routerLink="/welcome">Mon logo</span> <!-- <span *ngIf="!(isLoggedIn$ | async)" fxLayoutGap="15px" fxLayout="row" fxLayoutAlign="center center"> <mat-icon class="link" routerLink="/signin">login</mat-icon> <mat-icon class="link" routerLink="/signup">launch</mat-icon> diff --git a/src/app/share/models/bar.model.ts b/src/app/share/models/bar.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..b4d59963b72d777f81de243368982aee613cfb0b --- /dev/null +++ b/src/app/share/models/bar.model.ts @@ -0,0 +1,7 @@ +export interface Bar { + shortname: string; + longname: string; + description: string; + adress: string; + logo: string; +} \ No newline at end of file diff --git a/src/app/share/modules/core.module.ts b/src/app/share/modules/core.module.ts index 08f0ffdd164a398a1b6268d2d10f6d8950b9c89b..887ac0288c58ae8afd8ed02016faf60a0ead3b4c 100644 --- a/src/app/share/modules/core.module.ts +++ b/src/app/share/modules/core.module.ts @@ -21,10 +21,14 @@ import { TopbarComponent } from '../components/topbar/topbar.component'; // Interceptors import { AuthInterceptor } from '../interceptors/auth.interceptor'; import { ReactiveFormsModule } from '@angular/forms'; +import { WelcomeComponent } from 'src/app/components/welcome/welcome.component'; +import { BarsComponent } from 'src/app/components/bars/bars.component'; const COMPONENTS = [ SignupComponent, SigninComponent, + WelcomeComponent, + BarsComponent, TopbarComponent ]; diff --git a/src/app/share/services/auth.service.ts b/src/app/share/services/auth.service.ts index 522f5178bf955c36c6dfd43ccef6a58f9929f561..f71157d353b3fc67cace96f3536356e3420826e0 100644 --- a/src/app/share/services/auth.service.ts +++ b/src/app/share/services/auth.service.ts @@ -50,14 +50,14 @@ export class AuthService { } public refreshToken(): Observable<string> { - return this.http.get<string>('/api/auth/refresh_token'); + return this.http.get<string>('/api/v1/auth/refresh_token'); } public signup(user: User): Observable<User> { - return this.http.post<User>('/api/auth/signup', user); + return this.http.post<User>('/api/v1/auth/signup', user); } public signin(credentials: { email: string, password: string}): Observable<string> { - return this.http.post<string>('/api/auth/signin', credentials); + return this.http.post<string>('/api/v1/auth/signin', credentials); } } diff --git a/src/app/share/services/bar.service.ts b/src/app/share/services/bar.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..59a0c0a9c27f9f1a9b05b34213009ec0e99d592c --- /dev/null +++ b/src/app/share/services/bar.service.ts @@ -0,0 +1,17 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Bar } from '../models/bar.model'; + +@Injectable({ + providedIn: 'root' +}) +export class BarService { + + constructor(private http: HttpClient) {} + + // authInterceptor add the token in the HTTP request + public getAll(): Observable<Bar[]> { + return this.http.get<Bar[]>('/api/v1/bars'); + } +} diff --git a/src/app/share/services/notification.service.ts b/src/app/share/services/notification.service.ts index 867df9fd603e5ed7e4ba8d6887a23351a1b78825..07872369be78c76ab8bfd3c70ecd34a4a0975647 100644 --- a/src/app/share/services/notification.service.ts +++ b/src/app/share/services/notification.service.ts @@ -22,7 +22,7 @@ export class NotificationService { .then((sub: PushSubscription) => { console.log(`l'utilisateur a accepté les notifications`, sub); - this.http.post(`/api/notifications`, sub) + this.http.post(`/api/v1/notifications`, sub) .subscribe( () => { console.log(`La souscription a bien été enregistrée pour cet utilisateur`); @@ -37,6 +37,6 @@ export class NotificationService { } public sendTestNotification(): void { - this.http.get('/api/notifications/test').subscribe(); + this.http.get('/api/v1/notifications/test').subscribe(); } } diff --git a/src/app/share/services/user.service.ts b/src/app/share/services/user.service.ts index 8c4f6b548bd192cad2a76d2783c394132d43cbd4..095232e9947c190b283d75e1fce542e2040d915f 100644 --- a/src/app/share/services/user.service.ts +++ b/src/app/share/services/user.service.ts @@ -11,6 +11,6 @@ export class UserService { // on crée un interceptor pour ajouter le token dans la requete HTTP public getCurrentUser(): Observable<User> { - return this.http.get<User>('/api/user/current'); + return this.http.get<User>('/api/v1/user/current'); } } diff --git a/src/app/share/store/actions/bar.actions.ts b/src/app/share/store/actions/bar.actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..91ac24d702a973b0dbc2bf77c406964849366ab4 --- /dev/null +++ b/src/app/share/store/actions/bar.actions.ts @@ -0,0 +1,24 @@ +import { Action } from "@ngrx/store"; +import { Bar } from "../../models/bar.model"; + +export enum BarActionsTypes { + FETCH_BARS = '[bars/api] fetch all bars', + FETCH_BARS_SUCCESS = '[bars/api] fetch bars success', + FETCH_BARS_FAILED = '[bars/api] fetch bars failed' +} + +export class FetchBars implements Action { + readonly type = BarActionsTypes.FETCH_BARS; +} + +export class FetchBarsSuccess implements Action { + readonly type = BarActionsTypes.FETCH_BARS_SUCCESS; + constructor(public payload: Bar[]) {} +} + +export class FetchBarsFailed implements Action { + readonly type = BarActionsTypes.FETCH_BARS_FAILED; + constructor(public payload: string) {} +} + +export type BarsActions = FetchBars | FetchBarsSuccess | FetchBarsFailed; \ No newline at end of file diff --git a/src/app/share/store/effects/auth.effects.ts b/src/app/share/store/effects/auth.effects.ts index cc9fd7096b5e31cee0eee27e846dec10c6e64b26..90b9043cdf892c32443870ba1a2d2460591f7d67 100644 --- a/src/app/share/store/effects/auth.effects.ts +++ b/src/app/share/store/effects/auth.effects.ts @@ -15,7 +15,7 @@ import { HttpErrorResponse } from "@angular/common/http"; @Injectable() export class AuthEffects { - private subscription: Subscription; + private subscription: Subscription = null; constructor( private actions$: Actions, @@ -52,7 +52,6 @@ export class AuthEffects { exhaustMap((credentials: { email: string, password: string}) => this.authService.signin(credentials).pipe( map((token: string) => { - console.log('try signinsuccess'); return new SigninSuccess(token) }), catchError((error: HttpErrorResponse) => { @@ -79,16 +78,16 @@ export class AuthEffects { map((action: SigninSuccess) => action.payload), tap((token: string) => { localStorage.setItem('jwt', token); - - if (!this.subscription) { + + if (this.subscription === null) { this.subscription = this.authService.initRefreshToken().subscribe(); this.router.navigate(['/profile']); } }), ); - // Si on a pas de token, on a pas demander un refresh_token au back_end. - // On utilise donc le store dans l'effet pour vérifier (withLatestFrom) + // Si on a pas de token, on ne va pas demandé un refresh_token au back_end. + // On utilise donc le store dans l'effet pour le vérifier (withLatestFrom) @Effect() tryRefreshToken$ = this.actions$.pipe( ofType(AuthActionsTypes.TRY_REFRESH_TOKEN), @@ -100,6 +99,7 @@ export class AuthEffects { catchError((err: any) => { if(this.subscription) { this.subscription.unsubscribe(); + this.subscription = null; } localStorage.removeItem('jwt'); diff --git a/src/app/share/store/effects/bar.effects.ts b/src/app/share/store/effects/bar.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..634e67047b5e7892e0c78418ec3f48777e9e1dfd --- /dev/null +++ b/src/app/share/store/effects/bar.effects.ts @@ -0,0 +1,25 @@ +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { Injectable } from "@angular/core"; +import { catchError, map, mergeMap } from "rxjs/operators"; +import { EMPTY, of } from "rxjs"; + +import { BarActionsTypes, FetchBarsFailed, FetchBarsSuccess } from "../actions/bar.actions"; +import { BarService } from "../../services/bar.service"; +import { Bar } from "../../models/bar.model"; + +@Injectable() +export class BarEffects { + constructor( + private actions$: Actions, + private barService: BarService + ) {} + + loadBars$ = createEffect(() => this.actions$.pipe( + ofType(BarActionsTypes.FETCH_BARS), + mergeMap(() => this.barService.getAll() + .pipe( + map((bars: Bar[]) => (new FetchBarsSuccess(bars))), + catchError(() => of(new FetchBarsFailed('failed fetching all bars'))) + )) + )); +} \ No newline at end of file diff --git a/src/app/share/store/index.ts b/src/app/share/store/index.ts index 89f69ed922d05f4ac9a1db52517ace3552550cae..e1fbfc7afec046f8436196e55ad52bef251d75de 100644 --- a/src/app/share/store/index.ts +++ b/src/app/share/store/index.ts @@ -1,10 +1,13 @@ import { ActionReducerMap } from "@ngrx/store"; import { authReducer, AuthState } from "./reducers/auth.reducers"; +import { barReducer, BarState } from "./reducers/bar.reducers"; export interface State { auth: AuthState; + bars: BarState; } export const reducersMap: ActionReducerMap<State> = { - auth: authReducer + auth: authReducer, + bars: barReducer }; \ No newline at end of file diff --git a/src/app/share/store/reducers/bar.reducers.ts b/src/app/share/store/reducers/bar.reducers.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe643afdb904aea43c5ec93f08767afd1dba5f49 --- /dev/null +++ b/src/app/share/store/reducers/bar.reducers.ts @@ -0,0 +1,35 @@ +import { Bar } from "../../models/bar.model"; +import { BarsActions, BarActionsTypes } from "../actions/bar.actions"; + +export interface BarState { + bars: Bar[]; + currentBar: Bar; + error: string; +} + +const defaultBarState = { + bars: [], + currentBar: null, + error: '', +} + +export function barReducer(state: BarState = defaultBarState, action: BarsActions): BarState { + switch (action.type) { + + case BarActionsTypes.FETCH_BARS_SUCCESS: { + return { + ...state, + bars: action.payload + }; + } + + case BarActionsTypes.FETCH_BARS_FAILED: { + return { + ...state, + error: action.payload + }; + } + } + + return state; +} \ No newline at end of file diff --git a/src/app/share/store/selectors/bar.selectors.ts b/src/app/share/store/selectors/bar.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4f7a626459aa476ee0cb4c7795cf161f87f20f9 --- /dev/null +++ b/src/app/share/store/selectors/bar.selectors.ts @@ -0,0 +1,8 @@ +import { createFeatureSelector, createSelector } from "@ngrx/store"; +import { BarState } from "../reducers/bar.reducers"; + +export const baseSelector = createFeatureSelector('bars'); + +export const allBarsSelector = createSelector(baseSelector, (barState: BarState) => barState?.bars); + +export const currentBarSelector = createSelector(baseSelector, (barState: BarState) => barState?.currentBar); \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index 8bd0905cc2a0ac5957843aea127f3bc5561a7bef..28c4700ee95277b0c666fe08b202f1438796c8ba 100644 --- a/src/styles.css +++ b/src/styles.css @@ -2,7 +2,7 @@ @import '~@angular/material/prebuilt-themes/deeppurple-amber.css'; html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; background-color: #f1f1f1; text-align: center;} +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; background-color: #f1f1f1; text-align: center; color: #222222} .link { outline: 0; @@ -20,11 +20,17 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; background- .column-container { - flex-direction: column; + flex-flow: column nowrap; + padding: 5px; +} + +.row-container { + display: flex; + flex-flow: row nowrap; padding: 5px; } -.column-container > * { +.column-container > *, .row-container > * { margin: 5px; }