From 285f2088618918f8843b0cdea2dbbadc967ddbfb Mon Sep 17 00:00:00 2001 From: "Guilherme Siquinelli (guiseek)" Date: Sat, 20 Nov 2021 03:59:29 -0300 Subject: [PATCH] feat: sponsors front --- angular.json | 26 ++++++ .../controller/sponsors.controller.ts | 11 +++ .../sponsors/dto/create-sponsor.dto.ts | 48 ++++++---- .../sponsors/schema/sponsor.schema.ts | 88 +++++++++++++------ .../sponsors/service/sponsors.service.ts | 5 ++ apps/app/src/theming/_custom.scss | 1 + .../src/lib/+state/sponsor.facade.ts | 53 +++-------- .../src/lib/app-data-access.module.ts | 4 +- libs/app/data-access/src/lib/guards/index.ts | 1 + .../src/lib/guards/sponsors.guard.ts | 49 +++++++++++ .../infrastructure/sponsor-data.service.ts | 56 ++++-------- .../data-access/src/lib/interfaces/sponsor.ts | 24 ++--- libs/app/data-access/src/lib/mappers/index.ts | 2 + .../src/lib/mappers/sponsor.mapper.ts | 28 ++++++ .../src/lib/app-feature-shell.module.ts | 8 +- libs/app/feature-sponsors/.eslintrc.json | 42 +++++++++ libs/app/feature-sponsors/README.md | 7 ++ libs/app/feature-sponsors/jest.config.js | 20 +++++ libs/app/feature-sponsors/src/index.ts | 1 + .../src/lib/app-feature-sponsors.module.ts | 50 +++++++++++ .../google-form/google-form.component.html | 5 ++ .../google-form/google-form.component.scss | 11 +++ .../google-form/google-form.component.ts | 24 +++++ .../src/lib/containers/index.ts | 1 + .../sponsors/sponsors.container.html | 2 + .../sponsors/sponsors.container.scss | 0 .../containers/sponsors/sponsors.container.ts | 8 ++ .../feature-sponsors/src/lib/pages/index.ts | 2 + .../sponsor-page/sponsor-page.component.html | 84 ++++++++++++++++++ .../sponsor-page/sponsor-page.component.scss | 22 +++++ .../sponsor-page/sponsor-page.component.ts | 30 +++++++ .../sponsors-page.component.html | 13 +++ .../sponsors-page.component.scss | 0 .../sponsors-page/sponsors-page.component.ts | 25 ++++++ libs/app/feature-sponsors/src/test-setup.ts | 1 + libs/app/feature-sponsors/tsconfig.json | 24 +++++ libs/app/feature-sponsors/tsconfig.lib.json | 14 +++ libs/app/feature-sponsors/tsconfig.spec.json | 10 +++ .../src/lib/app-feature-talks.module.ts | 6 +- .../lib/containers/talks/talks.container.html | 2 +- libs/app/ui-layout/src/index.ts | 2 +- .../ui-layout/src/lib/app-ui-layout.module.ts | 16 ++-- .../app/ui-layout/src/lib/components/index.ts | 2 + .../main-toolbar/main-toolbar.component.html | 2 +- .../youtube-video.component.html | 7 ++ .../youtube-video.component.scss | 18 ++++ .../youtube-video/youtube-video.component.ts | 23 +++++ tsconfig.base.json | 3 + 48 files changed, 736 insertions(+), 145 deletions(-) create mode 100644 libs/app/data-access/src/lib/guards/sponsors.guard.ts create mode 100644 libs/app/data-access/src/lib/mappers/index.ts create mode 100644 libs/app/data-access/src/lib/mappers/sponsor.mapper.ts create mode 100644 libs/app/feature-sponsors/.eslintrc.json create mode 100644 libs/app/feature-sponsors/README.md create mode 100644 libs/app/feature-sponsors/jest.config.js create mode 100644 libs/app/feature-sponsors/src/index.ts create mode 100644 libs/app/feature-sponsors/src/lib/app-feature-sponsors.module.ts create mode 100644 libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.html create mode 100644 libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.scss create mode 100644 libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.ts create mode 100644 libs/app/feature-sponsors/src/lib/containers/index.ts create mode 100644 libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.html create mode 100644 libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.scss create mode 100644 libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.ts create mode 100644 libs/app/feature-sponsors/src/lib/pages/index.ts create mode 100644 libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.html create mode 100644 libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.scss create mode 100644 libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.ts create mode 100644 libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.html create mode 100644 libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.scss create mode 100644 libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.ts create mode 100644 libs/app/feature-sponsors/src/test-setup.ts create mode 100644 libs/app/feature-sponsors/tsconfig.json create mode 100644 libs/app/feature-sponsors/tsconfig.lib.json create mode 100644 libs/app/feature-sponsors/tsconfig.spec.json create mode 100644 libs/app/ui-layout/src/lib/components/index.ts create mode 100644 libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.html create mode 100644 libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.scss create mode 100644 libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.ts diff --git a/angular.json b/angular.json index d4d92f5..0abee42 100644 --- a/angular.json +++ b/angular.json @@ -382,6 +382,32 @@ }, "tags": [] }, + "app-feature-sponsors": { + "projectType": "library", + "root": "libs/app/feature-sponsors", + "sourceRoot": "libs/app/feature-sponsors/src", + "prefix": "speak-out", + "architect": { + "test": { + "builder": "@nrwl/jest:jest", + "outputs": ["coverage/libs/app/feature-sponsors"], + "options": { + "jestConfig": "libs/app/feature-sponsors/jest.config.js", + "passWithNoTests": true + } + }, + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "libs/app/feature-sponsors/src/**/*.ts", + "libs/app/feature-sponsors/src/**/*.html" + ] + } + } + }, + "tags": [] + }, "app-feature-talks": { "projectType": "library", "root": "libs/app/feature-talks", diff --git a/apps/api/src/features/sponsors/controller/sponsors.controller.ts b/apps/api/src/features/sponsors/controller/sponsors.controller.ts index 5aabf2e..22285df 100644 --- a/apps/api/src/features/sponsors/controller/sponsors.controller.ts +++ b/apps/api/src/features/sponsors/controller/sponsors.controller.ts @@ -55,6 +55,17 @@ export class SponsorsController { ); } + @Delete(':id') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth('access-token') + async delete( + @Param('id', ParseObjectIdPipe) id: string + ) { + return this.sponsorsService.delete( + await this.sponsorsService.validateSponsorById(id), + ); + } + @Post('join') @UseGuards(JwtAuthGuard) @ApiBearerAuth('access-token') diff --git a/apps/api/src/features/sponsors/dto/create-sponsor.dto.ts b/apps/api/src/features/sponsors/dto/create-sponsor.dto.ts index ae128c4..3aed979 100644 --- a/apps/api/src/features/sponsors/dto/create-sponsor.dto.ts +++ b/apps/api/src/features/sponsors/dto/create-sponsor.dto.ts @@ -16,44 +16,62 @@ export class CreateSponsorDto { }) @IsString() description: string; - + @ApiProperty({ type: String, required: true, }) @IsString() logo: string; - + @ApiProperty({ type: String, - required: true, + required: false, }) - @IsString() - slug: string; - + @IsUrl() + @IsOptional() + website: string; + @ApiProperty({ type: String, required: false, }) - @IsString() - color: string; - + @IsUrl() + @IsOptional() + youtube: string; + @ApiProperty({ type: String, required: false, }) @IsUrl() @IsOptional() - website: string; - + linkedin: string; + @ApiProperty({ type: String, required: false, }) @IsUrl() @IsOptional() - youtube: string; - + instagram: string; + + @ApiProperty({ + type: String, + required: false, + }) + @IsUrl() + @IsOptional() + facebook: string; + + @ApiProperty({ + type: String, + required: false, + }) + @IsUrl() + @IsOptional() + twitter: string; + @ApiProperty({ type: String, required: false, @@ -61,7 +79,7 @@ export class CreateSponsorDto { @IsUrl() @IsOptional() calendlyUrl: string; - + @ApiProperty({ type: String, required: false, @@ -69,7 +87,7 @@ export class CreateSponsorDto { @IsUrl() @IsOptional() videoUrl: string; - + @ApiProperty({ type: String, required: false, diff --git a/apps/api/src/features/sponsors/schema/sponsor.schema.ts b/apps/api/src/features/sponsors/schema/sponsor.schema.ts index 0dba078..57db693 100644 --- a/apps/api/src/features/sponsors/schema/sponsor.schema.ts +++ b/apps/api/src/features/sponsors/schema/sponsor.schema.ts @@ -7,27 +7,76 @@ import { Document } from 'mongoose'; @Schema() export class Sponsor extends Document { @Prop({ + type: String, required: true, }) name: string; - @Prop() + @Prop({ + type: String, + required: false, + }) description: string; - - @Prop() + + @Prop({ + type: String, + required: true, + }) logo: string; - - @Prop() - slug: string; - - @Prop() - color: string; - - @Prop() + + @Prop({ + type: String, + required: false, + }) website: string; - - @Prop() + + @Prop({ + type: String, + required: false, + }) youtube: string; + + @Prop({ + type: String, + required: false, + }) + linkedin: string; + + @Prop({ + type: String, + required: false, + }) + instagram: string; + + @Prop({ + type: String, + required: false, + }) + facebook: string; + + @Prop({ + type: String, + required: false, + }) + twitter: string; + + @Prop({ + type: String, + required: false, + }) + calendlyUrl: string; + + @Prop({ + type: String, + required: false, + }) + videoUrl: string; + + @Prop({ + type: String, + required: false, + }) + formUrl: string; @Prop({ type: [ @@ -38,19 +87,6 @@ export class Sponsor extends Document { ], }) members: User[]; - - // @ApiProperty() - // @Prop({ - // type: ObjectId, - // ref: Conf.name, - // }) - // conf: Conf; - - // @Prop({ - // type: ObjectId, - // ref: User.name, - // }) - // owner: User; } export const SponsorSchema = createSchemaForClassWithMethods(Sponsor); diff --git a/apps/api/src/features/sponsors/service/sponsors.service.ts b/apps/api/src/features/sponsors/service/sponsors.service.ts index e7f95fa..5b97cdc 100644 --- a/apps/api/src/features/sponsors/service/sponsors.service.ts +++ b/apps/api/src/features/sponsors/service/sponsors.service.ts @@ -46,6 +46,11 @@ export class SponsorsService { .findOneAndUpdate({ _id: sponsor._id, owner: user._id }, body); } + async delete(sponsor: Sponsor) { + return this.sponsorModel + .findByIdAndDelete(sponsor._id); + } + handleUpdateSponsor(sponsor: Sponsor, body: Partial) { this.sendMessage(sponsor, 'sponsor:update', Object.assign(sponsor, body)); } diff --git a/apps/app/src/theming/_custom.scss b/apps/app/src/theming/_custom.scss index 0cb2785..58cf60e 100644 --- a/apps/app/src/theming/_custom.scss +++ b/apps/app/src/theming/_custom.scss @@ -98,6 +98,7 @@ body { } .mat-toolbar { + gap: 12px; min-height: 64px; .mat-button { diff --git a/libs/app/data-access/src/lib/+state/sponsor.facade.ts b/libs/app/data-access/src/lib/+state/sponsor.facade.ts index 7e8f7c2..264ac44 100644 --- a/libs/app/data-access/src/lib/+state/sponsor.facade.ts +++ b/libs/app/data-access/src/lib/+state/sponsor.facade.ts @@ -1,18 +1,19 @@ -import { DomSanitizer } from '@angular/platform-browser'; import { SponsorDataService } from '../infrastructure'; import { SponsorVM, SponsorRaw } from '../interfaces'; -import { BaseState } from './base.state'; +import { SponsorMapper } from '../mappers'; import { Injectable } from '@angular/core'; +import { BaseState } from './base.state'; import { map } from 'rxjs/operators'; export interface SponsorState { loading: boolean; sponsors: SponsorVM[]; - sponsor: SponsorVM | null; + sponsor?: SponsorVM; } @Injectable() export class SponsorFacade extends BaseState { + loading$ = this.select((state) => state.loading); sponsors$ = this.select((state) => state.sponsors); @@ -20,31 +21,19 @@ export class SponsorFacade extends BaseState { constructor( private service: SponsorDataService, - private sanitizer: DomSanitizer + private mapper: SponsorMapper, ) { super({ loading: false, sponsors: [], - sponsor: null, }); } - loadUserSponsors() { - this.setState({ loading: true }); - this.service - .getUserSponsors() - .pipe(map((sponsors) => sponsors.map(this.mapTo))) - .subscribe((sponsors) => { - this.setState({ sponsors }); - this.setState({ loading: false }); - }); - } - loadSponsors() { this.setState({ loading: true }); this.service .getSponsors() - .pipe(map((sponsors) => sponsors.map(this.mapTo))) + .pipe(map((sponsors) => sponsors.map(this.mapper.mapTo))) .subscribe((sponsors) => { this.setState({ sponsors }); this.setState({ loading: false }); @@ -55,7 +44,7 @@ export class SponsorFacade extends BaseState { this.setState({ loading: true }); this.service .getSponsor(id) - .pipe(map(this.mapTo)) + .pipe(map(this.mapper.mapTo)) .subscribe((sponsor) => { this.setState({ sponsor }); this.setState({ loading: false }); @@ -66,7 +55,7 @@ export class SponsorFacade extends BaseState { this.setState({ loading: true }); this.service .updateSponsor(id, sponsor) - .pipe(map(this.mapTo)) + .pipe(map(this.mapper.mapTo)) .subscribe((sponsor) => { this.setState({ sponsor }); this.setState({ loading: false }); @@ -77,7 +66,7 @@ export class SponsorFacade extends BaseState { this.setState({ loading: true }); this.service .getSponsor(id) - .pipe(map(this.mapTo)) + .pipe(map(this.mapper.mapTo)) .subscribe((sponsor) => { this.setState({ sponsor }); this.setState({ loading: false }); @@ -88,39 +77,25 @@ export class SponsorFacade extends BaseState { this.setState({ loading: true }); this.service .leaveSponsor(id) - .pipe(map(this.mapTo)) + .pipe(map(this.mapper.mapTo)) .subscribe((sponsor) => { this.setState({ sponsor }); this.setState({ loading: false }); }); } - subscribeSponsor(sponsor: SponsorRaw) { - this.service.subscribeSponsor(sponsor); - } + // subscribeSponsor(sponsor: SponsorRaw) { + // this.service.subscribeSponsor(sponsor); + // } joinSponsor(sponsorId: string) { this.setState({ loading: true }); this.service .joinSponsor(sponsorId) - .pipe(map(this.mapTo)) + .pipe(map(this.mapper.mapTo)) .subscribe((sponsor) => { this.setState({ sponsor }); this.setState({ loading: false }); }); } - - mapTo({ _id, calendlyUrl, formUrl, videoUrl, ...sponsor }: SponsorRaw) { - return { - ...sponsor, - id: _id, - calendlyUrl: this.sanitize(calendlyUrl), - videoUrl: this.sanitize(videoUrl), - formUrl: this.sanitize(formUrl), - }; - } - - private sanitize(url: string) { - return this.sanitizer.bypassSecurityTrustResourceUrl(url); - } } diff --git a/libs/app/data-access/src/lib/app-data-access.module.ts b/libs/app/data-access/src/lib/app-data-access.module.ts index c0671d6..14151dc 100644 --- a/libs/app/data-access/src/lib/app-data-access.module.ts +++ b/libs/app/data-access/src/lib/app-data-access.module.ts @@ -25,12 +25,14 @@ import { SocialAuthServiceConfig, } from 'angularx-social-login'; import { - TalksGuard + TalksGuard, + SponsorsGuard } from './guards'; @NgModule({ providers: [ TalksGuard, + SponsorsGuard, AuthService, StorageData, AuthFacade, diff --git a/libs/app/data-access/src/lib/guards/index.ts b/libs/app/data-access/src/lib/guards/index.ts index fad4427..8e7c117 100644 --- a/libs/app/data-access/src/lib/guards/index.ts +++ b/libs/app/data-access/src/lib/guards/index.ts @@ -1,3 +1,4 @@ +export * from './sponsors.guard'; export * from './stream.guard'; export * from './talks.guard'; export * from './auth.guard'; diff --git a/libs/app/data-access/src/lib/guards/sponsors.guard.ts b/libs/app/data-access/src/lib/guards/sponsors.guard.ts new file mode 100644 index 0000000..bb9f70a --- /dev/null +++ b/libs/app/data-access/src/lib/guards/sponsors.guard.ts @@ -0,0 +1,49 @@ +import { + Route, + Router, + UrlTree, + CanLoad, + CanActivate, + RouterStateSnapshot, + ActivatedRouteSnapshot, +} from '@angular/router'; +import { AuthDataService } from '../infrastructure'; +import { Injectable } from '@angular/core'; +import { map, tap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +@Injectable() +export class SponsorsGuard implements CanActivate, CanLoad { + constructor( + private authService: AuthDataService, + private router: Router + ) { } + + canLoad(route: Route): Observable | Promise | boolean { + return this.checkAuth(route.path); + } + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | Promise | boolean { + return this.checkAuth(state.url); + } + + private checkAuth(url?: string) { + if (!this.authService.getAccessToken()) { + return this.router.navigate(['/auth', 'login']); + } + + return this.authService.getProfile() + .pipe( + map(user => !!user), + tap((auth) => { + if (!auth) { + const queryParams = url ? { returnUrl: url } : {}; + this.router.navigate(['/auth', 'login'], { queryParams }); + } + }) + ) + } +} diff --git a/libs/app/data-access/src/lib/infrastructure/sponsor-data.service.ts b/libs/app/data-access/src/lib/infrastructure/sponsor-data.service.ts index bfd5142..3739a41 100644 --- a/libs/app/data-access/src/lib/infrastructure/sponsor-data.service.ts +++ b/libs/app/data-access/src/lib/infrastructure/sponsor-data.service.ts @@ -1,6 +1,5 @@ import { AppConfig, APP_CONFIG } from '../app-data-access.config'; import { SocketService } from '../services/socket.service'; -import { getEntityWithSortedMembers } from '../utils'; import { Inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { SponsorRaw, User } from '../interfaces'; @@ -17,53 +16,38 @@ export class SponsorDataService { private config: AppConfig ) {} - getSponsor(sponsorId: string) { - return this.http - .get(`${this.config.api}/sponsor/id/${sponsorId}`) - .pipe(map(getEntityWithSortedMembers)); - } - - getPublicSponsors() { - return this.http.get(`${this.config.api}/sponsor/public`); - } - - getSponsorsByMember() { - return this.http.get(`${this.config.api}/sponsor/member`); + getSponsor(id: string) { + return this.http.get(`${this.config.api}/sponsors/${id}`); } - getUserSponsors() { - return this.http.get(`${this.config.api}/sponsor`); - } getSponsors() { - return this.http.get(`${this.config.api}/sponsor`); + return this.http.get(`${this.config.api}/sponsors`); } - createSponsor(sponsor: Partial) { - return this.http.post(`${this.config.api}/sponsor`, sponsor); + updateSponsor(id: string, updateSponsor: Partial) { + return this.http.put(`${this.config.api}/sponsors/${id}`, updateSponsor); } - deleteSponsor(sponsor: SponsorRaw) { - return this.http.delete(`${this.config.api}/sponsor/delete/${sponsor._id}`); + createSponsor(createSponsor: Omit) { + return this.http.post(`${this.config.api}/sponsors`, createSponsor); } - updateSponsor(id: string, sponsor: SponsorRaw) { - return this.http.put(`${this.config.api}/sponsor/${id}`, sponsor); + removeSponsor(id: string) { + return this.http.delete(`${this.config.api}/sponsors/${id}`); } joinSponsor(sponsorId: string) { return this.http - .post(`${this.config.api}/sponsor/join`, { sponsorId }) - .pipe(map(getEntityWithSortedMembers)); + .post(`${this.config.api}/sponsors/join`, { sponsorId }) + .pipe(map(this.getSponsorWithSortedMembers)); } leaveSponsor(sponsorId: string) { - return this.http.delete( - `${this.config.api}/sponsor/leave/${sponsorId}` - ); + return this.http.delete(`${this.config.api}/sponsors/leave/${sponsorId}`); } - subscribeSponsor(sponsor: SponsorRaw) { - return this.socket.emit('sponsor:subscribe', sponsor._id); + subscribeSponsor(sponsorId: string) { + this.socket.emit('sponsor:subscribe', sponsorId); } onLeaveEvent() { @@ -74,13 +58,11 @@ export class SponsorDataService { return this.socket.fromEvent('sponsor:join'); } - onUpdateEvent() { - return this.socket - .fromEvent('sponsor:update') - .pipe(map(getEntityWithSortedMembers)); - } + getSponsorWithSortedMembers(sponsor: SponsorRaw) { + sponsor.members = (sponsor.members ?? []).sort((a: any, b: any) => + a.online === b.online ? 0 : a.online ? -1 : b.online ? 1 : 0 + ); - onDeleteEvent() { - return this.socket.fromEvent('sponsor:delete'); + return sponsor; } } diff --git a/libs/app/data-access/src/lib/interfaces/sponsor.ts b/libs/app/data-access/src/lib/interfaces/sponsor.ts index 61313a4..6921c28 100644 --- a/libs/app/data-access/src/lib/interfaces/sponsor.ts +++ b/libs/app/data-access/src/lib/interfaces/sponsor.ts @@ -4,12 +4,16 @@ export interface Sponsor { name: string; description: string; logo: string; - slug: string; - color: string; - website: string; - youtube: string; - members: User[] | string[]; - owner: User | string; + website?: string; + youtube?: string; + linkedin?: string; + instagram?: string; + facebook?: string; + twitter?: string; + calendlyUrl?: string; + videoUrl?: string; + formUrl?: string; + members: User[]; } export interface SponsorRaw extends Sponsor { @@ -20,11 +24,11 @@ export interface SponsorRaw extends Sponsor { } // eslint-disable-next-line @typescript-eslint/no-empty-interface -interface SafeUrl {} +// interface SafeUrl {} export interface SponsorVM extends Sponsor { id: string; - calendlyUrl: SafeUrl; - videoUrl: SafeUrl; - formUrl: SafeUrl; + calendlyUrl: string; + videoUrl: string; + formUrl: string; } diff --git a/libs/app/data-access/src/lib/mappers/index.ts b/libs/app/data-access/src/lib/mappers/index.ts new file mode 100644 index 0000000..4d3dc2f --- /dev/null +++ b/libs/app/data-access/src/lib/mappers/index.ts @@ -0,0 +1,2 @@ +export * from './sponsor.mapper'; +export * from './talk.mapper'; diff --git a/libs/app/data-access/src/lib/mappers/sponsor.mapper.ts b/libs/app/data-access/src/lib/mappers/sponsor.mapper.ts new file mode 100644 index 0000000..891e855 --- /dev/null +++ b/libs/app/data-access/src/lib/mappers/sponsor.mapper.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { SponsorRaw, SponsorVM } from '../interfaces'; + +@Injectable({ providedIn: 'root' }) +export class SponsorMapper { + constructor(private sanitizer: DomSanitizer) {} + + mapTo(value: SponsorRaw): SponsorVM { + console.log(value); + + const { + _id = '', + calendlyUrl = '', + videoUrl = '', + formUrl = '', + ...talk + } = value; + + return { + id: _id, + calendlyUrl: calendlyUrl, + videoUrl: videoUrl, + formUrl: formUrl, + ...talk, + }; + } +} diff --git a/libs/app/feature-shell/src/lib/app-feature-shell.module.ts b/libs/app/feature-shell/src/lib/app-feature-shell.module.ts index 16f2e08..ce4dbbe 100644 --- a/libs/app/feature-shell/src/lib/app-feature-shell.module.ts +++ b/libs/app/feature-shell/src/lib/app-feature-shell.module.ts @@ -1,4 +1,4 @@ -import { AppDataAccessModule, AuthGuard, TalksGuard } from '@speak-out/app-data-access'; +import { AppDataAccessModule, TalksGuard } from '@speak-out/app-data-access'; import { SharedUiCommonModule } from '@speak-out/shared-ui-common'; import { AppUiLayoutModule } from '@speak-out/app-ui-layout'; import { NotFoundPageComponent } from './pages'; @@ -8,9 +8,7 @@ import { MainContainer } from './containers'; import { NgModule } from '@angular/core'; @NgModule({ - declarations: [ - MainContainer, - ], + declarations: [MainContainer], imports: [ CommonModule, AppUiLayoutModule, @@ -52,4 +50,4 @@ import { NgModule } from '@angular/core'; ]), ], }) -export class AppFeatureShellModule {} +export class AppFeatureShellModule { } diff --git a/libs/app/feature-sponsors/.eslintrc.json b/libs/app/feature-sponsors/.eslintrc.json new file mode 100644 index 0000000..562ccc5 --- /dev/null +++ b/libs/app/feature-sponsors/.eslintrc.json @@ -0,0 +1,42 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": [ + "plugin:@nrwl/nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "@angular-eslint/component-class-suffix": [ + "error", + { + "suffixes": ["Component", "Container", "Page"] + } + ], + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "speakOut", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "speak-out", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nrwl/nx/angular-template"], + "rules": {} + } + ] +} diff --git a/libs/app/feature-sponsors/README.md b/libs/app/feature-sponsors/README.md new file mode 100644 index 0000000..f8e8442 --- /dev/null +++ b/libs/app/feature-sponsors/README.md @@ -0,0 +1,7 @@ +# app-feature-sponsors + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test app-feature-sponsors` to execute the unit tests. diff --git a/libs/app/feature-sponsors/jest.config.js b/libs/app/feature-sponsors/jest.config.js new file mode 100644 index 0000000..7ae0ed2 --- /dev/null +++ b/libs/app/feature-sponsors/jest.config.js @@ -0,0 +1,20 @@ +module.exports = { + displayName: 'app-feature-sponsors', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + }, + coverageDirectory: '../../../coverage/libs/app/feature-sponsors', + transform: { + '^.+\\.(ts|js|html)$': 'jest-preset-angular', + }, + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/libs/app/feature-sponsors/src/index.ts b/libs/app/feature-sponsors/src/index.ts new file mode 100644 index 0000000..a61cfe9 --- /dev/null +++ b/libs/app/feature-sponsors/src/index.ts @@ -0,0 +1 @@ +export * from './lib/app-feature-sponsors.module'; diff --git a/libs/app/feature-sponsors/src/lib/app-feature-sponsors.module.ts b/libs/app/feature-sponsors/src/lib/app-feature-sponsors.module.ts new file mode 100644 index 0000000..0d431a8 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/app-feature-sponsors.module.ts @@ -0,0 +1,50 @@ +import { AppDataAccessModule, SponsorsGuard } from '@speak-out/app-data-access'; +import { SponsorPageComponent, SponsorsPageComponent } from './pages'; +import { SharedUiCommonModule } from '@speak-out/shared-ui-common'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { AppUiLayoutModule } from '@speak-out/app-ui-layout'; +import { MatSelectModule } from '@angular/material/select'; +import { LayoutModule } from '@angular/cdk/layout'; +import { SponsorsContainer } from './containers'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { GoogleFormComponent } from './components/google-form/google-form.component'; + +@NgModule({ + imports: [ + CommonModule, + LayoutModule, + MatSelectModule, + MatSidenavModule, + MatGridListModule, + AppUiLayoutModule, + AppDataAccessModule, + SharedUiCommonModule, + RouterModule.forChild([ + { + path: '', + canActivate: [SponsorsGuard], + component: SponsorsContainer, + children: [ + { + path: '', + component: SponsorsPageComponent, + }, + { + path: ':id', + component: SponsorPageComponent, + }, + ], + }, + ]), + ], + declarations: [ + SponsorsContainer, + SponsorPageComponent, + SponsorsPageComponent, + GoogleFormComponent, + ], +}) +export class AppFeatureSponsorsModule {} diff --git a/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.html b/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.html new file mode 100644 index 0000000..671f342 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.html @@ -0,0 +1,5 @@ + + + diff --git a/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.scss b/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.scss new file mode 100644 index 0000000..78ce07b --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.scss @@ -0,0 +1,11 @@ +:host { + flex: 1; + display: flex; + flex-direction: column; + + iframe { + width: 100%; + min-height: 600px; + flex: 1; + } +} \ No newline at end of file diff --git a/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.ts b/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.ts new file mode 100644 index 0000000..ce1a203 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/components/google-form/google-form.component.ts @@ -0,0 +1,24 @@ +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; + +@Component({ + selector: 'speak-out-google-form', + templateUrl: './google-form.component.html', + styleUrls: ['./google-form.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class GoogleFormComponent implements OnInit { + @Input() url: string | null = null; + + safeUrl!: SafeUrl; + + constructor(private sanintizer: DomSanitizer) {} + + ngOnInit(): void { + if (this.url) this.safeUrl = this.sanitize(this.url); + } + + sanitize(url: string) { + return this.sanintizer.bypassSecurityTrustResourceUrl(url); + } +} diff --git a/libs/app/feature-sponsors/src/lib/containers/index.ts b/libs/app/feature-sponsors/src/lib/containers/index.ts new file mode 100644 index 0000000..c546332 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/containers/index.ts @@ -0,0 +1 @@ +export * from './sponsors/sponsors.container'; diff --git a/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.html b/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.html new file mode 100644 index 0000000..a693b2b --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.scss b/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.scss new file mode 100644 index 0000000..e69de29 diff --git a/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.ts b/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.ts new file mode 100644 index 0000000..5507506 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/containers/sponsors/sponsors.container.ts @@ -0,0 +1,8 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + templateUrl: './sponsors.container.html', + styleUrls: ['./sponsors.container.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SponsorsContainer { } diff --git a/libs/app/feature-sponsors/src/lib/pages/index.ts b/libs/app/feature-sponsors/src/lib/pages/index.ts new file mode 100644 index 0000000..899577e --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/pages/index.ts @@ -0,0 +1,2 @@ +export * from './sponsors-page/sponsors-page.component'; +export * from './sponsor-page/sponsor-page.component'; diff --git a/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.html b/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.html new file mode 100644 index 0000000..47b4e73 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.html @@ -0,0 +1,84 @@ + +
+ + + + + + + + + + +
+ +
\ No newline at end of file diff --git a/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.scss b/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.scss new file mode 100644 index 0000000..7bdfbf2 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.scss @@ -0,0 +1,22 @@ +@use 'libs/styles/mixins' as mixin; + +:host { + flex: 1; + display: flex; + margin: 0 auto; + max-width: 1200px; + background-color: #fbfcfd; + + @include mixin.media(mobile) { + flex-direction: column-reverse; + } + + .mat-card { + flex: 1; + margin: 25px; + } + + section .mat-card { + padding: 0; + } +} diff --git a/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.ts b/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.ts new file mode 100644 index 0000000..8a3797a --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/pages/sponsor-page/sponsor-page.component.ts @@ -0,0 +1,30 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { SponsorFacade } from '@speak-out/app-data-access'; +import { map } from 'rxjs/operators'; + +@Component({ + templateUrl: './sponsor-page.component.html', + styleUrls: ['./sponsor-page.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SponsorPageComponent implements OnInit { + isHandset$ = this.breakpointObserver.observe(Breakpoints.Handset).pipe( + map(({ matches }) => matches ? 2 : 1) + ); + + constructor( + private breakpointObserver: BreakpointObserver, + readonly facade: SponsorFacade, + private route: ActivatedRoute + ) { } + + ngOnInit(): void { + this.loadSponsor(this.route.snapshot); + } + + loadSponsor({ params }: ActivatedRouteSnapshot) { + if (params.id) this.facade.loadSponsor(params.id); + } +} diff --git a/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.html b/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.html new file mode 100644 index 0000000..b9384b9 --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.scss b/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.ts b/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.ts new file mode 100644 index 0000000..aa4793b --- /dev/null +++ b/libs/app/feature-sponsors/src/lib/pages/sponsors-page/sponsors-page.component.ts @@ -0,0 +1,25 @@ +import { SponsorFacade } from '@speak-out/app-data-access'; +import { Component, OnInit } from '@angular/core'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { map } from 'rxjs/operators'; + +@Component({ + templateUrl: './sponsors-page.component.html', + styleUrls: ['./sponsors-page.component.scss'] +}) +export class SponsorsPageComponent implements OnInit { + isHandset$ = this.breakpointObserver.observe(Breakpoints.Handset).pipe( + map(({ matches }) => matches) + ); + + constructor( + private breakpointObserver: BreakpointObserver, + readonly facade: SponsorFacade + ) { + + } + + ngOnInit() { + this.facade.loadSponsors(); + } +} diff --git a/libs/app/feature-sponsors/src/test-setup.ts b/libs/app/feature-sponsors/src/test-setup.ts new file mode 100644 index 0000000..1100b3e --- /dev/null +++ b/libs/app/feature-sponsors/src/test-setup.ts @@ -0,0 +1 @@ +import 'jest-preset-angular/setup-jest'; diff --git a/libs/app/feature-sponsors/tsconfig.json b/libs/app/feature-sponsors/tsconfig.json new file mode 100644 index 0000000..5358376 --- /dev/null +++ b/libs/app/feature-sponsors/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "angularCompilerOptions": { + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/libs/app/feature-sponsors/tsconfig.lib.json b/libs/app/feature-sponsors/tsconfig.lib.json new file mode 100644 index 0000000..4002dc8 --- /dev/null +++ b/libs/app/feature-sponsors/tsconfig.lib.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "target": "es2015", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [], + "lib": ["dom", "es2018"] + }, + "exclude": ["src/test-setup.ts", "**/*.spec.ts"], + "include": ["**/*.ts"] +} diff --git a/libs/app/feature-sponsors/tsconfig.spec.json b/libs/app/feature-sponsors/tsconfig.spec.json new file mode 100644 index 0000000..fd405a6 --- /dev/null +++ b/libs/app/feature-sponsors/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/libs/app/feature-talks/src/lib/app-feature-talks.module.ts b/libs/app/feature-talks/src/lib/app-feature-talks.module.ts index 432f3fe..09121b9 100644 --- a/libs/app/feature-talks/src/lib/app-feature-talks.module.ts +++ b/libs/app/feature-talks/src/lib/app-feature-talks.module.ts @@ -48,7 +48,10 @@ import { SponsorsPageComponent } from './pages/sponsors-page/sponsors-page.compo }, { path: 'patrocinadores', - component: SponsorsPageComponent, + loadChildren: () => + import('@speak-out/app-feature-sponsors').then( + (module) => module.AppFeatureSponsorsModule + ), }, { path: ':id', @@ -56,6 +59,7 @@ import { SponsorsPageComponent } from './pages/sponsors-page/sponsors-page.compo }, ], }, + ]), ], declarations: [ diff --git a/libs/app/feature-talks/src/lib/containers/talks/talks.container.html b/libs/app/feature-talks/src/lib/containers/talks/talks.container.html index db79405..6cf1208 100644 --- a/libs/app/feature-talks/src/lib/containers/talks/talks.container.html +++ b/libs/app/feature-talks/src/lib/containers/talks/talks.container.html @@ -29,9 +29,9 @@ Speak Out +

Speak Out

-

Speak Out

diff --git a/libs/app/ui-layout/src/index.ts b/libs/app/ui-layout/src/index.ts index 025c623..65d696f 100644 --- a/libs/app/ui-layout/src/index.ts +++ b/libs/app/ui-layout/src/index.ts @@ -1,2 +1,2 @@ -export * from './lib/components/main-toolbar/main-toolbar.component'; export * from './lib/app-ui-layout.module'; +export * from './lib/components'; diff --git a/libs/app/ui-layout/src/lib/app-ui-layout.module.ts b/libs/app/ui-layout/src/lib/app-ui-layout.module.ts index 154fac7..4a25bfa 100644 --- a/libs/app/ui-layout/src/lib/app-ui-layout.module.ts +++ b/libs/app/ui-layout/src/lib/app-ui-layout.module.ts @@ -1,12 +1,12 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { CommonModule } from '@angular/common'; -import { MatToolbarModule } from '@angular/material/toolbar'; +import { MainToolbarComponent, YoutubeVideoComponent } from './components'; import { MatButtonModule } from '@angular/material/button'; +import { MatToolbarModule } from '@angular/material/toolbar'; import { MatDividerModule } from '@angular/material/divider'; -import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; -import { MainToolbarComponent } from './components/main-toolbar/main-toolbar.component'; +import { MatIconModule } from '@angular/material/icon'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; @NgModule({ imports: [ @@ -18,7 +18,7 @@ import { MainToolbarComponent } from './components/main-toolbar/main-toolbar.com MatDividerModule, MatToolbarModule, ], - declarations: [MainToolbarComponent], - exports: [MainToolbarComponent], + declarations: [MainToolbarComponent, YoutubeVideoComponent], + exports: [MainToolbarComponent, YoutubeVideoComponent], }) export class AppUiLayoutModule {} diff --git a/libs/app/ui-layout/src/lib/components/index.ts b/libs/app/ui-layout/src/lib/components/index.ts new file mode 100644 index 0000000..0edf446 --- /dev/null +++ b/libs/app/ui-layout/src/lib/components/index.ts @@ -0,0 +1,2 @@ +export * from './youtube-video/youtube-video.component'; +export * from './main-toolbar/main-toolbar.component'; diff --git a/libs/app/ui-layout/src/lib/components/main-toolbar/main-toolbar.component.html b/libs/app/ui-layout/src/lib/components/main-toolbar/main-toolbar.component.html index 0c80549..867681f 100644 --- a/libs/app/ui-layout/src/lib/components/main-toolbar/main-toolbar.component.html +++ b/libs/app/ui-layout/src/lib/components/main-toolbar/main-toolbar.component.html @@ -1,9 +1,9 @@ +

Speak Out

-

Speak Out

diff --git a/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.html b/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.html new file mode 100644 index 0000000..e191444 --- /dev/null +++ b/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.html @@ -0,0 +1,7 @@ + diff --git a/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.scss b/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.scss new file mode 100644 index 0000000..eb6ea0e --- /dev/null +++ b/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.scss @@ -0,0 +1,18 @@ +:host { + display: block; + position: relative; + padding-bottom: 56.25%; /* 16:9 */ + height: 0; + + background-position: center; + background-repeat: no-repeat; + background-size: contain; + + iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +} diff --git a/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.ts b/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.ts new file mode 100644 index 0000000..d0dce18 --- /dev/null +++ b/libs/app/ui-layout/src/lib/components/youtube-video/youtube-video.component.ts @@ -0,0 +1,23 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; + +@Component({ + selector: 'app-youtube-video', + templateUrl: './youtube-video.component.html', + styleUrls: ['./youtube-video.component.scss'], +}) +export class YoutubeVideoComponent implements OnInit { + @Input() url: string | null = null; + + safeUrl!: SafeUrl; + + constructor(private sanintizer: DomSanitizer) {} + + ngOnInit(): void { + if (this.url) this.safeUrl = this.sanitize(this.url); + } + + sanitize(url: string) { + return this.sanintizer.bypassSecurityTrustResourceUrl(url); + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 53563d9..9f644e1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -42,6 +42,9 @@ "@speak-out/app-ui-dialogs": ["libs/app/ui-dialogs/src/index.ts"], "@speak-out/app-ui-layout": ["libs/app/ui-layout/src/index.ts"], "@speak-out/app-ui-talks": ["libs/app/ui-talks/src/index.ts"], + "@speak-out/app-feature-sponsors": [ + "libs/app/feature-sponsors/src/index.ts" + ], "@speak-out/shared-ui-common": ["libs/shared/ui-common/src/index.ts"], "@speak-out/shared-ui-dialogs": ["libs/shared/ui-dialogs/src/index.ts"], "@speak-out/shared-util-storage": [