Skip to content

Commit

Permalink
Add saved venues for all visitors
Browse files Browse the repository at this point in the history
  • Loading branch information
CatboyEngineering committed Dec 30, 2024
1 parent a325bac commit ed682fe
Show file tree
Hide file tree
Showing 21 changed files with 265 additions and 29 deletions.
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
</script>
<!-- End Single Page Apps for GitHub Pages -->
<style>*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}.bg-mmSand{--tw-bg-opacity: 1;background-color:rgb(242 229 191 / var(--tw-bg-opacity))}@media (prefers-color-scheme: dark){.dark\:bg-mmClubBlack{--tw-bg-opacity: 1;background-color:rgb(29 30 32 / var(--tw-bg-opacity))}}
</style><link rel="stylesheet" href="styles-YKZ3SHTD.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-YKZ3SHTD.css"></noscript></head>
</style><link rel="stylesheet" href="styles-NH3S2B7K.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-NH3S2B7K.css"></noscript></head>

<body class="bg-mmSand dark:bg-mmClubBlack">
<app-root></app-root>
<script src="polyfills-FFHMD2TL.js" type="module"></script><script src="main-TROSQLHT.js" type="module"></script></body>
<script src="polyfills-FFHMD2TL.js" type="module"></script><script src="main-NWBCMMDI.js" type="module"></script></body>
</html>
46 changes: 23 additions & 23 deletions docs/main-TROSQLHT.js → docs/main-NWBCMMDI.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/styles-YKZ3SHTD.css → docs/styles-NH3S2B7K.css

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AdminStateEffects } from './store/admin-state/admin-state.effects';
import { AppDetailsStateEffects } from './store/app-details-state/app-details-state.effects';
import { AuthStateEffects } from './store/auth-state/auth-state.effects';
import { localstorageMetaReducer } from './store/localstorage-meta.reducer';
import { SavedStateEffects } from './store/saved-state/saved-state.effects';
import { rootReducer } from './store/store';
import { VenueStateEffects } from './store/venue-state/venue-state.effects';

Expand All @@ -26,7 +27,7 @@ export const appConfig: ApplicationConfig = {
})
),
provideStore(rootReducer, { metaReducers: metaReducers }),
provideEffects([AuthStateEffects, AppDetailsStateEffects, VenueStateEffects, AdminStateEffects]),
provideEffects([AuthStateEffects, AppDetailsStateEffects, VenueStateEffects, AdminStateEffects, SavedStateEffects]),
provideStoreDevtools({ maxAge: 25, logOnly: !environment.production }),
provideHttpClient(withFetch()),
{
Expand Down
5 changes: 5 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { PrivacyComponent } from './components/pages/privacy/privacy.component';
import { RegisterComponent } from './components/pages/register/register.component';
import { ReportSubmittedComponent } from './components/pages/report-submitted/report-submitted.component';
import { ReportComponent } from './components/pages/report/report.component';
import { SavedVenuesComponent } from './components/pages/saved-venues/saved-venues.component';
import { TermsComponent } from './components/pages/terms/terms.component';
import { VerifyCharacterSuccessComponent } from './components/pages/verify-character-success/verify-character-success.component';
import { VerifyCharacterComponent } from './components/pages/verify-character/verify-character.component';
Expand All @@ -37,6 +38,10 @@ export const routes: Routes = [
path: 'register',
component: RegisterComponent
},
{
path: 'saved',
component: SavedVenuesComponent
},
{
path: 'verify-character',
component: VerifyCharacterComponent,
Expand Down
Empty file.
16 changes: 16 additions & 0 deletions src/app/components/pages/saved-venues/saved-venues.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="text-center">
<h1 class="text-5xl text-mmRust my-2 font-FFXIV uppercase">Attuned Venues</h1>
<p class="text-sm my-1">These are the venues you've saved on MiqoMixers.</p>
</div>

<div *ngIf="savedVenues$ | async as venues; else emptyList">
<ng-container *ngIf="venues.length > 0; else emptyList">
<div *ngFor="let venue of venues">
<app-venue-post [venue]="venue"></app-venue-post>
</div>
</ng-container>
</div>

<ng-template #emptyList>
<p class="text-center pt-5 font-bold text-mmRust dark:text-mmSand">You haven't saved any venues yet.</p>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SavedVenuesComponent } from './saved-venues.component';

describe('SavedVenuesComponent', () => {
let component: SavedVenuesComponent;
let fixture: ComponentFixture<SavedVenuesComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SavedVenuesComponent]
})
.compileComponents();

fixture = TestBed.createComponent(SavedVenuesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
25 changes: 25 additions & 0 deletions src/app/components/pages/saved-venues/saved-venues.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { map, Observable, withLatestFrom } from 'rxjs';
import { CombinedVenue } from '../../../models/combined-venue.interface';
import { SavedStateService } from '../../../store/saved-state/saved-state.service';
import { VenueStateService } from '../../../store/venue-state/venue-state.service';
import { VenuePostComponent } from '../../ui/venue-post/venue-post.component';

@Component({
selector: 'app-saved-venues',
standalone: true,
imports: [CommonModule, VenuePostComponent],
templateUrl: './saved-venues.component.html',
styleUrl: './saved-venues.component.css'
})
export class SavedVenuesComponent {
savedVenues$: Observable<CombinedVenue[]>;

constructor(private venueStateService: VenueStateService, private savedStateService: SavedStateService) {
this.savedVenues$ = savedStateService.savedVenues$.pipe(
withLatestFrom(venueStateService.venues$),
map(([savedVenues, venues]) => venues.filter(venue => savedVenues.find(sv => sv === venue.venue.venueID)))
);
}
}
8 changes: 8 additions & 0 deletions src/app/components/ui/nav/nav.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
<div class="flex-auto"></div>
<div class="basis-full md:basis-auto flex-initial self-center">
<ul class="text-center md:text-inherit border-t md:border-none py-2 md:py-0">
<li
class="inline-block mx-2 my-1 hover:text-mmOrange hover:cursor-pointer active:text-mmBlue dark:hover:text-mmClubLavender dark:active:text-mmClubBlue"
[routerLink]="['/saved']"
(click)="expanded = false">
<i class="fa-solid fa-bookmark mx-1.5"></i>
<p class="inline-block">Saved</p>
</li>

<ng-container *ngIf="isLoggedIn$ | async; else guestUser">
<li
class="inline-block mx-2 my-1 hover:text-mmOrange hover:cursor-pointer active:text-mmBlue dark:hover:text-mmClubLavender dark:active:text-mmClubBlue"
Expand Down
19 changes: 19 additions & 0 deletions src/app/components/ui/venue-post/venue-post.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,25 @@

<!-- Collapsed Details -->
<div class="p-3 rounded-bl-md rounded-br-md bg-gray-200 text-sm dark:bg-mmClubSpace" *ngIf="expanded">
<ng-container *ngIf="isSaved$ | async; else notSaved">
<button (click)="unsave()">
<div
class="bg-mmRust hover:bg-mmDarkRust active:bg-mmClubLavender dark:bg-mmBlue dark:hover:bg-mmDarkBlue dark:active:bg-mmDarkerBlue px-2 py-1 rounded-md mb-3 mr-1">
<i class="inline-block fa-solid fa-bookmark mx-1 text-red-900 dark:text-red-500"></i>
<p class="inline-block text-mmSand dark:text-gray-50 font-bold">Unsave</p>
</div>
</button>
</ng-container>
<ng-template #notSaved>
<button (click)="save()">
<div
class="bg-mmRust hover:bg-mmDarkRust active:bg-mmClubLavender dark:bg-mmBlue dark:hover:bg-mmDarkBlue dark:active:bg-mmDarkerBlue px-2 py-1 rounded-md mb-3 mr-1">
<i class="inline-block fa-solid fa-bookmark mx-1 text-mmSand"></i>
<p class="inline-block text-mmSand dark:text-gray-50 font-bold">Save</p>
</div>
</button>
</ng-template>

<button *ngIf="isLoggedIn$ | async" (click)="star()">
<div
class="bg-mmRust hover:bg-mmDarkRust active:bg-mmClubLavender dark:bg-mmBlue dark:hover:bg-mmDarkBlue dark:active:bg-mmDarkerBlue px-2 py-1 rounded-md mb-3">
Expand Down
18 changes: 17 additions & 1 deletion src/app/components/ui/venue-post/venue-post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { VenueHoursStatus } from '../../../models/enum/venue-hours-status.enum';
import { ChipSortPipe } from '../../../pipes/chip-sort-pipe/chip-sort.pipe';
import { TimePipe } from '../../../pipes/time-pipe/time.pipe';
import { AuthStateService } from '../../../store/auth-state/auth-state.service';
import { SavedStateService } from '../../../store/saved-state/saved-state.service';
import { VenueStateService } from '../../../store/venue-state/venue-state.service';
import { ChipComponent } from '../chip/chip.component';
import { CopyButtonComponent } from '../copy-button/copy-button.component';
Expand All @@ -27,6 +28,7 @@ export class VenuePostComponent implements OnInit {
@Input() expanded: boolean = false;

isLoggedIn$: Observable<boolean>;
isSaved$: Observable<boolean>;

venueHoursStatus: VenueHoursStatus;
borderColor: string;
Expand All @@ -36,14 +38,20 @@ export class VenuePostComponent implements OnInit {
FormName = FormName;
VenueHoursStatus = VenueHoursStatus;

constructor(private venueStateService: VenueStateService, private authStateService: AuthStateService) {}
constructor(
private venueStateService: VenueStateService,
private authStateService: AuthStateService,
private savedStateService: SavedStateService
) {}

ngOnInit(): void {
this.isLoggedIn$ = this.authStateService.authToken$.pipe(
withLatestFrom(this.authStateService.isCharacterVerified$),
map(([token, isVerified]) => !!token && !!isVerified)
);

this.isSaved$ = this.savedStateService.savedVenues$.pipe(map(saved => !!saved.find(v => v === this.venue.venue.venueID)));

this.venueHoursStatus = this.venue.venue.hoursStatus!;
this.setBorderColor();
}
Expand Down Expand Up @@ -72,6 +80,14 @@ export class VenuePostComponent implements OnInit {
this.venueStateService.onStarVenue(this.venue.venue.venueID);
}

save() {
this.savedStateService.onSaveVenue(this.venue);
}

unsave() {
this.savedStateService.onUnsaveVenue(this.venue);
}

private setBorderColor() {
switch (this.venueHoursStatus) {
case VenueHoursStatus.CLOSED:
Expand Down
2 changes: 2 additions & 0 deletions src/app/store/app-state.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AuthState } from './auth-state/auth-state.interface';
import { SavedState } from './saved-state/saved-state.interface';

export interface AppState {
authState: AuthState;
savedState: SavedState;
}
5 changes: 5 additions & 0 deletions src/app/store/saved-state/saved-initial-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SavedState } from './saved-state.interface';

export const savedInitialState: SavedState = {
savedVenues: []
};
11 changes: 11 additions & 0 deletions src/app/store/saved-state/saved-state.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createAction, props } from '@ngrx/store';
import { CombinedVenue } from '../../models/combined-venue.interface';

export abstract class SavedStateActions {
static readonly saveAttempt = createAction('@miqomixers/action/save/attempt', props<{ venue: CombinedVenue }>());
static readonly saveSuccess = createAction('@miqomixers/action/save/success', props<{ venue: CombinedVenue }>());
static readonly unsaveAttempt = createAction('@miqomixers/action/unsave/attempt', props<{ venue: CombinedVenue }>());
static readonly unsaveSuccess = createAction('@miqomixers/action/unsave/success', props<{ venue: CombinedVenue }>());
static readonly clearAttempt = createAction('@miqomixers/action/save/clear/attempt');
static readonly clearSuccess = createAction('@miqomixers/action/save/clear/success');
}
33 changes: 33 additions & 0 deletions src/app/store/saved-state/saved-state.effects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map } from 'rxjs';
import { SavedStateActions } from './saved-state.actions';
import { SavedStateService } from './saved-state.service';

@Injectable()
export class SavedStateEffects {
constructor(private actions$: Actions, private savedStateService: SavedStateService) {}

saveVenueAttempt$ = createEffect(() =>
this.actions$.pipe(
ofType(SavedStateActions.saveAttempt),
map(action => SavedStateActions.saveSuccess(action))
)
);

unsaveVenueAttempt$ = createEffect(() =>
this.actions$.pipe(
ofType(SavedStateActions.unsaveAttempt),
map(action => SavedStateActions.unsaveSuccess(action))
)
);

clearVenuesAttempt$ = createEffect(() =>
this.actions$.pipe(
ofType(SavedStateActions.clearAttempt),
map(action => SavedStateActions.clearSuccess())
)
);

// For success, maybe show a notification
}
3 changes: 3 additions & 0 deletions src/app/store/saved-state/saved-state.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface SavedState {
savedVenues: string[];
}
35 changes: 35 additions & 0 deletions src/app/store/saved-state/saved-state.reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createReducer, on } from '@ngrx/store';
import { savedInitialState } from './saved-initial-state';
import { SavedStateActions } from './saved-state.actions';
import { SavedState } from './saved-state.interface';

export const savedStateReducer = createReducer(
savedInitialState,
on(
SavedStateActions.saveSuccess,
(state, action): SavedState => ({
...state,
savedVenues: [...state.savedVenues, action.venue.venue.venueID]
})
),
on(
SavedStateActions.clearSuccess,
(state, action): SavedState => ({
...state,
savedVenues: []
})
),
on(SavedStateActions.unsaveSuccess, (state, action): SavedState => {
var newList = [...state.savedVenues];
var i = newList.findIndex(v => v === action.venue.venue.venueID);

if (i >= 0) {
newList.splice(i, 1);
}

return {
...state,
savedVenues: newList
};
})
);
5 changes: 5 additions & 0 deletions src/app/store/saved-state/saved-state.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { SavedState } from './saved-state.interface';

export const selectSavedState = createFeatureSelector<SavedState>('savedState');
export const selectSavedVenues = createSelector(selectSavedState, (state: SavedState) => state.savedVenues);
27 changes: 27 additions & 0 deletions src/app/store/saved-state/saved-state.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { CombinedVenue } from '../../models/combined-venue.interface';
import { SavedStateActions } from './saved-state.actions';
import { selectSavedVenues } from './saved-state.selectors';

@Injectable({
providedIn: 'root'
})
export class SavedStateService {
savedVenues$: Observable<string[]> = this.store.select(selectSavedVenues);

constructor(private store: Store) {}

onSaveVenue(venue: CombinedVenue): void {
this.store.dispatch(SavedStateActions.saveAttempt({ venue }));
}

onUnsaveVenue(venue: CombinedVenue): void {
this.store.dispatch(SavedStateActions.unsaveAttempt({ venue }));
}

onClearSaved(): void {
this.store.dispatch(SavedStateActions.clearAttempt());
}
}
4 changes: 3 additions & 1 deletion src/app/store/store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { authStateReducer } from './auth-state/auth-state.reducer';
import { savedStateReducer } from './saved-state/saved-state.reducer';

export const rootReducer = {
authState: authStateReducer
authState: authStateReducer,
savedState: savedStateReducer
};

0 comments on commit ed682fe

Please sign in to comment.