diff --git a/apps/commudle-admin/src/app/app-routing.module.ts b/apps/commudle-admin/src/app/app-routing.module.ts index 8d26f8664a..b5c287e103 100644 --- a/apps/commudle-admin/src/app/app-routing.module.ts +++ b/apps/commudle-admin/src/app/app-routing.module.ts @@ -132,6 +132,10 @@ const routes: Routes = [ loadChildren: () => import('./feature-modules/public-community/public-community.module').then((m) => m.PublicCommunityModule), }, + { + path: 'auth/users', + loadChildren: () => import('./feature-modules/user-oauth/user-oauth.module').then((m) => m.UserOauthModule), + }, { path: 'communities/:community_id/events/:event_id', loadChildren: () => @@ -142,6 +146,11 @@ const routes: Routes = [ loadChildren: () => import('./feature-modules/public-hackathon/public-hackathon.module').then((m) => m.PublicHackathonModule), }, + { + path: 'communities/:community_id/quests/:quest_id', + loadChildren: () => + import('./feature-modules/public-quests/public-quests.module').then((m) => m.PublicQuestsModule), + }, { path: 'communities/:community_id/channels', loadChildren: () => @@ -264,6 +273,13 @@ const routes: Routes = [ path: 'communities/:community_id/event-dashboard', loadChildren: () => import('./feature-modules/events/events.module').then((m) => m.EventsModule), }, + { + path: 'communities/:community_id/quest-dashboard', + loadChildren: () => + import('./feature-modules/quests-control-panel/quests-control-panel.module').then( + (m) => m.QuestsControlPanelModule, + ), + }, { path: 'orgs', loadChildren: () => diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel-routing.module.ts b/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel-routing.module.ts index 861f5c68d7..c27c33fffe 100644 --- a/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel-routing.module.ts +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel-routing.module.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommunityAdminNotificationsComponent } from 'apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-admin-notifications/community-admin-notifications.component'; @@ -24,6 +25,8 @@ import { AdminCommunityHackathonComponent } from './components/admin-community-h import { CommunityBankDetailsComponent } from 'apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-payments/community-bank-details/community-bank-details.component'; import { CommunityPaymentLogsComponent } from 'apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-payments/community-payment-logs/community-payment-logs.component'; import { PaymentLogEdfegComponent } from 'apps/shared-components/payment-detail/payment-log-edfeg/payment-log-edfeg.component'; +import { CommunityQuestComponent } from 'apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component'; +import { QuestFormComponent } from 'apps/shared-components/quest-form/quest-form.component'; const routes = [ { path: 'new', @@ -136,6 +139,10 @@ const routes = [ path: 'team', component: CommunityTeamComponent, }, + { + path: 'quests', + component: CommunityQuestComponent, + }, { path: 'channels', component: CommunityChannelsAndForumsComponent, diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel.module.ts b/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel.module.ts index 265e2ccc5c..0c26d4a53f 100644 --- a/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel.module.ts +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/community-control-panel.module.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -58,6 +59,7 @@ import { CommunityChannelsModule } from 'apps/commudle-admin/src/app/feature-mod import { AdminCommunityHackathonComponent } from './components/admin-community-hackathon/admin-community-hackathon.component'; import { CommunityPaymentLogsComponent } from 'apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-payments/community-payment-logs/community-payment-logs.component'; import { CommunityBankDetailsComponent } from 'apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-payments/community-bank-details/community-bank-details.component'; +import { CommunityQuestComponent } from 'apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component'; @NgModule({ declarations: [ @@ -86,6 +88,7 @@ import { CommunityBankDetailsComponent } from 'apps/commudle-admin/src/app/featu AdminCommunityHackathonComponent, CommunityPaymentLogsComponent, CommunityBankDetailsComponent, + CommunityQuestComponent, ], imports: [ CommonModule, diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.html b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.html index e00b48cdd4..274d546a9b 100644 --- a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.html +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.html @@ -159,8 +159,12 @@

{{ community.name }}

Members + + + Quests + - + Forms diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.ts b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.ts index b93647993e..fd4a184a36 100644 --- a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.ts +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-control-panel/community-control-panel.component.ts @@ -7,7 +7,7 @@ import { ICommunity } from 'apps/shared-models/community.model'; import { EemailTypes } from 'apps/shared-models/enums/email_types.enum'; import { SeoService } from 'apps/shared-services/seo.service'; import { Subscription } from 'rxjs'; -import { faScroll } from '@fortawesome/free-solid-svg-icons'; +import { faScroll, faShapes } from '@fortawesome/free-solid-svg-icons'; import { NotificationsStore } from 'apps/commudle-admin/src/app/feature-modules/notifications/store/notifications.store'; import { GoogleTagManagerService } from 'apps/commudle-admin/src/app/services/google-tag-manager.service'; import { ENotificationSenderTypes } from 'apps/shared-models/enums/notification_sender_types.enum'; @@ -27,7 +27,6 @@ export class CommunityControlPanelComponent implements OnInit, OnDestroy { notificationCount = 0; ENotificationSenderTypes = ENotificationSenderTypes; - faScroll = faScroll; subscriptions: Subscription[] = []; icons = { @@ -35,6 +34,8 @@ export class CommunityControlPanelComponent implements OnInit, OnDestroy { faFileLines, faNewspaper, faMessage, + faShapes, + faScroll, }; environment = environment; darkMode: boolean; diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.html b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.html new file mode 100644 index 0000000000..3afc6ce7b7 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.html @@ -0,0 +1,57 @@ + +
+ + + + + + + + + + + + + + + + + + + +
NameCreation dateDatesNo of tasksNo of participantsActions
{{ quest.name }}{{ moment(quest.created_at).format('dddd, MMM Do, YYYY') }} +
+ Start at: {{ moment(quest.start_date).format('dddd, MMM Do, YYYY') }} +
+ End at: {{ moment(quest.end_date).format('dddd, MMM Do, YYYY') }} +
+ + No Date selected yet! + +
{{ quest.tasks_count }}{{ quest.participants_count }} + +
+ +
diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.scss b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.scss new file mode 100644 index 0000000000..f2e51c368c --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.scss @@ -0,0 +1,24 @@ +section { + @apply com-p-6; + + table { + @apply com-border-collapse com-w-full com-mt-4; + + td, + th { + @apply com-border com-border-solid com-border-Bright-Gray com-text-left com-p-2; + } + + tr:nth-child(even) { + @apply com-bg-Bright-Gray; + } + + .action-buttons { + @apply com-flex com-justify-evenly; + } + } + + .pagination { + @apply com-mt-4; + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.spec.ts new file mode 100644 index 0000000000..a9d8287f54 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { CommunityQuestComponent } from './community-quest.component'; + +describe('CommunityQuestComponent', () => { + let component: CommunityQuestComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CommunityQuestComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityQuestComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.ts b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.ts new file mode 100644 index 0000000000..a391e1432f --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/community-control-panel/components/community-quest/community-quest.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { EDbModels, IQuest } from '@commudle/shared-models'; +import { ExternalUserAuthHandlerService, QuestService } from '@commudle/shared-services'; +import { faPlus, faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons'; +import moment from 'moment'; + +@Component({ + selector: 'commudle-community-quest', + templateUrl: './community-quest.component.html', + styleUrls: ['./community-quest.component.scss'], +}) +export class CommunityQuestComponent implements OnInit { + communityId: number | string; + quests: IQuest[]; + moment = moment; + page = 1; + total = 0; + count = 10; + icons = { + faPlus, + faArrowUpRightFromSquare, + }; + + constructor( + private externalUserAuthHandlerService: ExternalUserAuthHandlerService, + private questService: QuestService, + private activatedRoute: ActivatedRoute, + ) {} + + ngOnInit() { + this.activatedRoute.parent.params.subscribe((params) => { + this.communityId = params.community_id; + this.getQuests(); + }); + } + + loginToDiscord() { + this.externalUserAuthHandlerService.loginToDiscord(); + } + + getQuests() { + this.questService.index(EDbModels.KOMMUNITY, this.communityId, this.page, this.count).subscribe((data) => { + this.quests = data.values; + this.page = data.page; + this.total = data.total; + }); + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/public-community/components/home-community/home-community.component.html b/apps/commudle-admin/src/app/feature-modules/public-community/components/home-community/home-community.component.html index 52f17019b0..fd0e0990e2 100644 --- a/apps/commudle-admin/src/app/feature-modules/public-community/components/home-community/home-community.component.html +++ b/apps/commudle-admin/src/app/feature-modules/public-community/components/home-community/home-community.component.html @@ -232,6 +232,10 @@

{{ community.name }}

/> Hackathons
+ + + Quest + +
+ +
+ +
+
+
+ + +
+ + +
+ + + + + + + Basic Information + Add basic information such as title, description, rewards + + + +
+ + +
+
+ + +
+ + +
+
+ +
+ +
+ +
+
+
+
+
+ + + + + + + + diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.scss b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.scss new file mode 100644 index 0000000000..97231b76f2 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.scss @@ -0,0 +1,80 @@ +.heading-container { + .heading { + @apply com-text-base com-font-semibold com-text-gray-800; + } + .sub-heading { + @apply com-text-sm com-font-medium com-text-gray-400; + } +} + +.social-media-cards { + @apply com-grid com-grid-cols-3 com-gap-6 com-mt-4; + .social-media-card { + @apply com-p-4 com-h-32 com-border com-border-solid com-border-gray-200 com-rounded-xl com-cursor-pointer hover:com-shadow; + .logo-title { + @apply com-flex com-gap-3 com-items-center; + .logo { + @apply com-h-6; + img { + @apply com-h-full; + } + } + .title { + @apply com-text-Yankees-Blue com-text-base com-font-semibold; + } + } + .description { + @apply com-text-xs com-font-normal com-my-3 com-text-gray-800; + } + } +} + +.tasks { + @apply com-grid com-grid-cols-1 md:com-grid-cols-3; +} + +.task-form { + @apply com-w-full md:com-w-[40dvw] com-max-h-[90dvh]; + nb-card-header, + nb-card-footer { + @apply com-flex com-justify-between com-items-center; + } + + .platform-container { + @apply com-flex com-gap-3 com-items-center com-bg-primary-100 com-w-full com-rounded-xl com-p-4; + p { + @apply com-text-base com-font-semibold com-text-primary-600 com-m-0; + } + img { + @apply com-h-6; + } + } + form { + .required { + @apply com-text-red-500; + } + nb-accordion { + @apply com-my-4 com-shadow-none com-border com-border-solid com-border-Bright-Gray com-rounded-xl; + nb-accordion-item { + @apply com-rounded-xl; + nb-accordion-item-header { + @apply com-flex-col com-items-start; + .heading { + @apply com-text-sm com-font-semibold com-inline-block; + } + + .sub-heading { + @apply com-text-sm com-font-normal; + } + } + } + } + + .select { + @apply com-flex com-items-center com-gap-1 com-h-8 com-p-1 com-text-sm com-rounded-[4px] com-cursor-pointer com-bg-[#F6F9FC] hover:com-bg-[#eef2f7]/50 com-border com-border-solid com-border-[#E4E9F2] com-w-full; + select { + @apply com-bg-[#F6F9FC] hover:com-bg-[#eef2f7]/50 com-cursor-pointer com-border-0 com-w-full; + } + } + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.spec.ts new file mode 100644 index 0000000000..ac69648cd3 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { QuestControlPanelTasksComponent } from './quest-control-panel-tasks.component'; + +describe('QuestControlPanelTasksComponent', () => { + let component: QuestControlPanelTasksComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [QuestControlPanelTasksComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestControlPanelTasksComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.ts new file mode 100644 index 0000000000..88b44f81c9 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel-tasks/quest-control-panel-tasks.component.ts @@ -0,0 +1,99 @@ +import { Component, OnInit, TemplateRef } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { IQuest, IQuestTaskType, ITask } from '@commudle/shared-models'; +import { TaskService } from '@commudle/shared-services'; +import { NbDialogService } from '@commudle/theme'; + +@Component({ + selector: 'commudle-quest-control-panel-tasks', + templateUrl: './quest-control-panel-tasks.component.html', + styleUrls: ['./quest-control-panel-tasks.component.scss'], +}) +export class QuestControlPanelTasksComponent implements OnInit { + questTaskType: IQuestTaskType[]; + taskForm: FormGroup; + selectedTaskType: IQuestTaskType; + quest: IQuest; + tasks: ITask[]; + + constructor( + private taskService: TaskService, + private dialogService: NbDialogService, + private fb: FormBuilder, + private activatedRoute: ActivatedRoute, + ) { + this.taskForm = this.fb.group({ + name: ['', Validators.required], + description: ['', Validators.required], + url: ['', Validators.required], + reward_points: [0, [Validators.required, Validators.min(1)]], + other_rewards: [''], + quest_task_type_id: [0, [Validators.required, Validators.min(1)]], + }); + } + + ngOnInit() { + this.activatedRoute.parent.data.subscribe((data: { quest: IQuest }) => { + this.quest = data.quest; + this.getTasks(); + }); + this.getTaskTypesIndex(); + } + + getTasks() { + this.taskService.index(this.quest.id).subscribe((res) => { + this.tasks = res; + }); + } + + getTaskTypesIndex() { + this.taskService.getTaskTypesIndex().subscribe((res) => { + this.questTaskType = res; + }); + } + + openDialogBox(dialog: TemplateRef, taskType: IQuestTaskType, task?: ITask, index?: number) { + if (taskType) { + this.selectedTaskType = taskType; + } + if (task) { + this.taskForm.patchValue({ + name: task.name, + description: task.description, + url: task.url, + reward_points: task.reward_points, + other_rewards: task.other_rewards, + quest_task_type_id: task.quest_task_type.id, + }); + } else { + this.resetForm(); + } + this.dialogService.open(dialog, { + context: { + taskType: taskType, + task: task, + index: index, + }, + }); + } + + resetForm() { + this.taskForm.patchValue({ + name: '', + description: '', + url: '', + reward_points: 0, + other_rewards: '', + quest_task_type_id: 0, + }); + } + + createTask() { + this.taskService.createTask(this.quest.id, this.taskForm.value).subscribe((res: ITask) => { + this.tasks.unshift(res); + }); + } + + updateTask() {} +} diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.html b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.html new file mode 100644 index 0000000000..3dee83b0dc --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.html @@ -0,0 +1,68 @@ +
+ +
+

{{ quest.name }}

+
+

Quest

+
+
+
+ +
+ +
+ +
+
diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.scss b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.scss new file mode 100644 index 0000000000..f04e83ed48 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.scss @@ -0,0 +1,82 @@ +.hackathon-details { + @apply com-p-8 com-bg-white com-border-0 com-border-b com-border-solid com-border-Bright-Gray; + > div { + @apply com-flex com-gap-4 com-items-center com-mt-2; + h1 { + @apply com-text-3xl com-m-0; + } + .badge { + @apply com-py-2 com-px-3 com-rounded-[32px] com-border com-border-solid com-border-purple-700 com-bg-purple-50; + p { + @apply com-m-0 com-text-purple-700 com-text-base; + } + } + } +} + +.sidebar { + @apply com-w-80 com-sticky com-top-16 com-h-[calc(100vh-70px)]; + + a { + @apply com-text-inherit com-no-underline; + } + .action-buttons { + @apply com-py-3; + + .back-admin { + @apply com-px-4; + .icon { + @apply com-text-Cadet-Grey; + } + .heading { + @apply com-text-xs com-text-Cadet-Grey; + } + } + + .status-buttons { + @apply com-flex com-flex-col com-justify-center; + .border { + @apply com-border com-border-Bright-Gray com-border-solid com-rounded-md; + } + + section { + @apply com-flex com-justify-center com-mb-5; + } + } + .category { + @apply com-flex com-flex-col; + } + .category-heading { + @apply com-text-sm com-font-semibold com-my-2 com-px-4; + } + .divided-line { + @apply com-border com-border-solid com-border-Bright-Gray; + } + .action-button { + @apply com-px-5 com-my-1 com-flex com-gap-2 com-items-center com-cursor-pointer; + &.active { + @apply com-bg-Bright-Gray-Light com-text-tYankees-Blue; + } + .icon { + fa-icon { + @apply com-text-lg com-text-Cadet-Grey; + } + } + .action-button-name { + @apply com-text-sm com-font-light; + } + } + + .action-button:hover { + @apply com-bg-Bright-Gray-Light com-text-tYankees-Blue; + } + } +} + +a { + @apply com-flex com-gap-2 com-items-center com-py-1; +} + +.router { + @apply com-w-full com-my-2 com-mx-4; +} diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.spec.ts new file mode 100644 index 0000000000..3830dad70d --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { QuestControlPanelComponent } from './quest-control-panel.component'; + +describe('QuestControlPanelComponent', () => { + let component: QuestControlPanelComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [QuestControlPanelComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestControlPanelComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.ts new file mode 100644 index 0000000000..90857e9a8b --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-control-panel/quest-control-panel.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ICommunity, IQuest } from '@commudle/shared-models'; +import { QuestService } from '@commudle/shared-services'; +import { + faArrowLeft, + faArrowUpRightFromSquare, + faChartPie, + faListCheck, + faInfoCircle, + faGear, +} from '@fortawesome/free-solid-svg-icons'; +import { CommunitiesService } from 'apps/commudle-admin/src/app/services/communities.service'; +import { FooterService } from 'apps/commudle-admin/src/app/services/footer.service'; +import { ESidebarWidth } from 'apps/shared-components/sidebar/enum/sidebar.enum'; + +@Component({ + selector: 'commudle-quest-control-panel', + templateUrl: './quest-control-panel.component.html', + styleUrls: ['./quest-control-panel.component.scss'], +}) +export class QuestControlPanelComponent implements OnInit { + questSlug: string; + quest: IQuest; + communitySlug: string; + community: ICommunity; + ESidebarWidth = ESidebarWidth; + icons = { faArrowLeft, faArrowUpRightFromSquare, faChartPie, faListCheck, faInfoCircle, faGear }; + constructor( + private questService: QuestService, + private activatedRoute: ActivatedRoute, + private communityService: CommunitiesService, + private footerService: FooterService, + ) {} + + ngOnInit() { + this.footerService.changeMiniFooterStatus(false); + this.activatedRoute.params.subscribe((params) => { + this.questSlug = params['quest_id']; + this.communitySlug = params['community_id']; + this.getCommunity(); + this.getQuest(); + }); + } + + // TODO: get data from resolver + getQuest() { + this.questService.getQuest(this.questSlug).subscribe((quest) => { + this.quest = quest; + }); + } + + getCommunity() { + this.communityService.getCommunityDetails(this.communitySlug).subscribe((data) => { + this.community = data; + }); + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.html b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.html new file mode 100644 index 0000000000..464f78bbfd --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.html @@ -0,0 +1 @@ +

Dashboard

diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.scss b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.spec.ts new file mode 100644 index 0000000000..524f1b522a --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { QuestDashboardComponent } from './quest-dashboard.component'; + +describe('QuestDashboardComponent', () => { + let component: QuestDashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [QuestDashboardComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestDashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.ts new file mode 100644 index 0000000000..ac6909f052 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/components/quest-dashboard/quest-dashboard.component.ts @@ -0,0 +1,12 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'commudle-quest-dashboard', + templateUrl: './quest-dashboard.component.html', + styleUrls: ['./quest-dashboard.component.scss'], +}) +export class QuestDashboardComponent implements OnInit { + constructor() {} + + ngOnInit() {} +} diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/quests-control-panel.module.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/quests-control-panel.module.ts new file mode 100644 index 0000000000..25ecb26617 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/quests-control-panel.module.ts @@ -0,0 +1,44 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { QuestsControlPanelRoutes } from './quests-control-panel.routing'; +import { SharedComponentsModule } from 'apps/shared-components/shared-components.module'; +import { NewQuestFormComponent } from './components/new-quest-form/new-quest-form.component'; +import { QuestDashboardComponent } from './components/quest-dashboard/quest-dashboard.component'; +import { SidebarComponent } from 'apps/shared-components/sidebar/sidebar.component'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { QuestControlPanelComponent } from './components/quest-control-panel/quest-control-panel.component'; +import { QuestControlPanelTasksComponent } from './components/quest-control-panel-tasks/quest-control-panel-tasks.component'; +import { EditQuestComponent } from './components/edit-quest/edit-quest.component'; +import { NbButtonModule, NbCardModule, NbIconModule, NbAccordionModule, NbInputModule } from '@commudle/theme'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { SharedPipesModule } from 'apps/shared-pipes/pipes.module'; + +@NgModule({ + imports: [ + CommonModule, + QuestsControlPanelRoutes, + SharedComponentsModule, + FontAwesomeModule, + FormsModule, + ReactiveFormsModule, + SharedPipesModule, + + // standalone + SidebarComponent, + + // Nebular + NbCardModule, + NbIconModule, + NbButtonModule, + NbAccordionModule, + NbInputModule, + ], + declarations: [ + NewQuestFormComponent, + QuestDashboardComponent, + QuestControlPanelTasksComponent, + QuestControlPanelComponent, + EditQuestComponent, + ], +}) +export class QuestsControlPanelModule {} diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/quests-control-panel.routing.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/quests-control-panel.routing.ts new file mode 100644 index 0000000000..abcb1a8921 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/quests-control-panel.routing.ts @@ -0,0 +1,35 @@ +import { Routes, RouterModule } from '@angular/router'; +import { NewQuestFormComponent } from './components/new-quest-form/new-quest-form.component'; +import { QuestDashboardComponent } from './components/quest-dashboard/quest-dashboard.component'; +import { QuestControlPanelTasksComponent } from './components/quest-control-panel-tasks/quest-control-panel-tasks.component'; +import { QuestControlPanelComponent } from './components/quest-control-panel/quest-control-panel.component'; +import { EditQuestComponent } from './components/edit-quest/edit-quest.component'; +import { QuestDetailsResolver } from './resolver/quest-details.resolver'; + +const routes: Routes = [ + { + path: 'new', + component: NewQuestFormComponent, + }, + { + path: ':quest_id', + component: QuestControlPanelComponent, + resolve: { quest: QuestDetailsResolver }, + children: [ + { + path: '', + component: QuestDashboardComponent, + }, + { + path: 'tasks', + component: QuestControlPanelTasksComponent, + }, + { + path: 'edit', + component: EditQuestComponent, + }, + ], + }, +]; + +export const QuestsControlPanelRoutes = RouterModule.forChild(routes); diff --git a/apps/commudle-admin/src/app/feature-modules/quests-control-panel/resolver/quest-details.resolver.ts b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/resolver/quest-details.resolver.ts new file mode 100644 index 0000000000..653d1feec0 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/quests-control-panel/resolver/quest-details.resolver.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { ApiRoutesService } from 'apps/shared-services/api-routes.service'; +import { API_ROUTES } from 'apps/shared-services/api-routes.constants'; +import { IQuest } from '@commudle/shared-models'; + +@Injectable({ + providedIn: 'root', +}) +export class QuestDetailsResolver implements Resolve { + constructor(private http: HttpClient, private apiRoutesService: ApiRoutesService) {} + + resolve(route: ActivatedRouteSnapshot): Observable { + const questId = route.parent.params.quest_id || route.params.quest_id; + const params = new HttpParams().set('quest_id', questId); + return this.http.get(this.apiRoutesService.getRoute(API_ROUTES.QUESTS.SHOW), { params }); + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.html b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.html index 43f57c09d9..6b4e36b60c 100644 --- a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.html +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.html @@ -6,10 +6,28 @@ Create Assets +
+
+ +

No Result found....

+
+
+ +
@@ -28,7 +46,30 @@
- +
+ + + + +
Delete Page
+ +
+ + + +
+
{{ searchedById | json }}
+
+ + + + +
+
diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.scss b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.scss index 6a1b044668..5797a4f19f 100644 --- a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.scss +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.scss @@ -11,6 +11,12 @@ @apply com-p-0; .assets-list-container { + .search-container { + @apply com-mx-6; + h3 { + @apply com-text-center; + } + } nb-list { nb-list-item { &:first-of-type { @@ -66,3 +72,21 @@ .asset-image img { @apply com-h-[200px]; } + +.loading-spinner { + @apply com-my-6; +} + +.get-by-id { + @apply com-w-[40dvw]; + nb-card-header, + nb-card-footer { + @apply com-flex com-justify-between com-items-center; + } + .json-content { + white-space: pre-wrap; /* Preserve formatting and allow wrapping */ + word-wrap: break-word; /* Break long words like URLs */ + overflow-wrap: break-word; /* Compatibility for modern browsers */ + max-width: 100%; /* Optional: Limit the width */ + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.ts b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.ts index 0ed12d86ab..4994389c27 100644 --- a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.ts +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-static-assets/admin-static-assets-list/admin-static-assets-list.component.ts @@ -1,7 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, TemplateRef } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { NbDialogService } from '@commudle/theme'; +import { AdminStaticAssetsService } from 'apps/commudle-admin/src/app/feature-modules/sys-admin/services/admin-static-assets.service'; import { IStaticAsset } from 'apps/shared-models/assets.model'; -import { AdminStaticAssetsService } from '../../../services/admin-static-assets.service'; import { Subscription } from 'rxjs'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; @Component({ selector: 'app-admin-static-assets-list', @@ -9,27 +12,65 @@ import { Subscription } from 'rxjs'; styleUrls: ['./admin-static-assets-list.component.scss'], }) export class AdminStaticAssetsListComponent implements OnInit { - constructor(private adminStaticAssetsService: AdminStaticAssetsService) {} + constructor(private adminStaticAssetsService: AdminStaticAssetsService, private dialogService: NbDialogService) {} assets: IStaticAsset[] = []; page = 1; count = 5; total = -1; subscriptions: Subscription[] = []; + searchControl = new FormControl(''); + searchById = new FormControl(''); + isLoading = true; + searchedById; ngOnInit(): void { + this.setupSearch(); this.getAsset(); } - getAsset(): void { + setupSearch(): void { + this.subscriptions.push( + this.searchControl.valueChanges + .pipe( + debounceTime(2000), // Wait for 2 seconds + distinctUntilChanged(), // Prevent duplicate requests for the same value + ) + .subscribe((searchTerm) => { + this.isLoading = true; + this.assets = []; // Clear current assets + this.page = 1; // Reset pagination + this.getAsset(searchTerm); // Fetch filtered assets + }), + ); + } + + getAsset(query = ''): void { if (this.assets.length !== this.total) { this.subscriptions.push( - this.adminStaticAssetsService.getAssets(this.page, this.count).subscribe((value) => { - this.assets = this.assets.concat(value.static_assets); - this.page = +value.page; - this.total = +value.total; - this.page += 1; - }), + this.adminStaticAssetsService.getAssets(this.page, this.count, query).subscribe( + (value) => { + this.assets = this.assets.concat(value.static_assets); + this.page = +value.page; + this.total = +value.total; + this.page += 1; + this.isLoading = false; + }, + (error) => { + console.error('Error fetching assets:', error); + this.isLoading = false; + }, + ), ); } } + + getAssetById() { + this.adminStaticAssetsService.getAssetById(Number(this.searchById.value)).subscribe((data) => { + this.searchedById = data; + }); + } + + openDialogBox(templateRef: TemplateRef) { + this.dialogService.open(templateRef, { closeOnBackdropClick: false }); + } } diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/community-controls.component.html b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/community-controls.component.html index 89427f2c42..ad6b39c07d 100644 --- a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/community-controls.component.html +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/community-controls.component.html @@ -11,6 +11,8 @@ Create Community + + diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.html b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.html new file mode 100644 index 0000000000..455dd92ee4 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.html @@ -0,0 +1,143 @@ +
+
+
+ + +

List of Quest Task types platform

+ + + + + + + + + + + + + + + + +
LogoPlatform NameDescriptionPlatform TypesActions
{{ taskType.platform_name | titlecase }}{{ taskType.description ?? '--' }}{{ taskType.platform_type | capitalizeAndRemoveUnderscore }} + +       + +
+
+
+ + + + +
{{ data.taskType ? 'Edit Task type' : 'Create new task type' }}
+ +
+ +
+
+
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+
+
+
+ + + + +
+
+
+
+
diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.scss b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.scss new file mode 100644 index 0000000000..5c12acdae4 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.scss @@ -0,0 +1,42 @@ +table { + font-family: arial, sans-serif; + border-collapse: collapse; + width: 100%; +} + +td, +th { + border: 1px solid #dddddd; + text-align: left; + padding: 8px; +} + +tr:nth-child(even) { + background-color: #dddddd; +} + +img { + @apply com-h-6 com-w-6; +} + +ul { + @apply com-my-0; +} +.edit-create-task-types { + @apply md:com-w-[40dvw] com-w-full; + nb-card-header, + nb-card-footer { + @apply com-flex com-justify-between; + } + + nb-card-body { + .quest-task-type-form { + .form-group { + @apply com-mb-4; + } + .required { + @apply com-text-red-500; + } + } + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.spec.ts new file mode 100644 index 0000000000..3e1b6a12a0 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { QuestTaskTypesComponent } from './quest-task-types.component'; + +describe('QuestTaskTypesComponent', () => { + let component: QuestTaskTypesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [QuestTaskTypesComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestTaskTypesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.ts b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.ts new file mode 100644 index 0000000000..6f52cf9bab --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component.ts @@ -0,0 +1,59 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { IQuestTaskType } from '@commudle/shared-models'; +import { TaskService } from '@commudle/shared-services'; +import { NbDialogService } from '@commudle/theme'; + +@Component({ + selector: 'commudle-quest-task-types', + templateUrl: './quest-task-types.component.html', + styleUrls: ['./quest-task-types.component.scss'], +}) +export class QuestTaskTypesComponent implements OnInit { + taskTypes: IQuestTaskType[]; + questTaskTypeForm: FormGroup; + + constructor(private taskService: TaskService, private nbDialogService: NbDialogService, private fb: FormBuilder) { + this.questTaskTypeForm = this.fb.group({ + platform_name: ['', [Validators.required, Validators.maxLength(100)]], + platform_type: ['', [Validators.required]], + logo_url: ['', [Validators.required, Validators.pattern('https?://.+')]], // URL validation + description: ['', [Validators.required, Validators.maxLength(255)]], + }); + } + + ngOnInit() { + this.taskService.getQuestTaskTypeIndex().subscribe((res) => { + this.taskTypes = res; + }); + } + + openDialog(templateRef, taskType?) { + if (taskType) { + this.questTaskTypeForm.patchValue(taskType); + } + this.nbDialogService.open(templateRef, { + context: { + taskType: taskType, + }, + }); + } + createOrUpdate(taskType?) { + if (taskType) { + this.taskService.updateQuestTaskType(taskType.id, this.questTaskTypeForm.value).subscribe((res) => { + const index = this.taskTypes.findIndex((t) => t.id === taskType.id); + this.taskTypes[index] = res; + }); + } else { + this.taskService.createQuestTaskType(this.questTaskTypeForm.value).subscribe((res) => { + this.taskTypes.unshift(res); + }); + } + } + + destroy(taskType, index) { + this.taskService.destroyQuestTaskType(taskType.id).subscribe((res) => { + this.taskTypes.splice(index, 1); + }); + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/services/admin-static-assets.service.ts b/apps/commudle-admin/src/app/feature-modules/sys-admin/services/admin-static-assets.service.ts index 4ca2278de4..02bb415452 100644 --- a/apps/commudle-admin/src/app/feature-modules/sys-admin/services/admin-static-assets.service.ts +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/services/admin-static-assets.service.ts @@ -12,7 +12,7 @@ import { Observable } from 'rxjs'; export class AdminStaticAssetsService { constructor(private http: HttpClient, private apiRoutesService: ApiRoutesService) {} - getAssets(page?: number, count?: number): Observable { + getAssets(page?: number, count?: number, q?): Observable { let params = new HttpParams(); if (page) { params = params.append('page', String(page)); @@ -20,7 +20,10 @@ export class AdminStaticAssetsService { if (count) { params = params.append('count', String(count)); } - return this.http.get(this.apiRoutesService.getRoute(API_ROUTES.STATIC_ASSETS.SHOW), { params }); + if (q) { + params = params.append('q', q); + } + return this.http.get(this.apiRoutesService.getRoute(API_ROUTES.STATIC_ASSETS.INDEX), { params }); } createAsset(formData): Observable { diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin-routing.module.ts b/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin-routing.module.ts index 04450cc08c..32f07dab37 100644 --- a/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin-routing.module.ts +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin-routing.module.ts @@ -24,6 +24,7 @@ import { AdminFeaturedUsersComponent } from 'apps/commudle-admin/src/app/feature import { AdminFeaturedCommunitiesChannelsComponent } from 'apps/commudle-admin/src/app/feature-modules/sys-admin/components/admin-featured/admin-featured-communities-channels/admin-featured-communities-channels.component'; import { PaymentLogsComponent } from 'apps/commudle-admin/src/app/feature-modules/sys-admin/components/payment-logs/payment-logs.component'; import { PaymentDetailComponent } from 'apps/shared-components/payment-detail/payment-detail.component'; +import { QuestTaskTypesComponent } from 'apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component'; const routes = [ { @@ -34,6 +35,10 @@ const routes = [ path: '', component: CommunityControlsComponent, }, + { + path: 'quest-task-types', + component: QuestTaskTypesComponent, + }, { path: 'pa', component: AdminPageAdsComponent, diff --git a/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin.module.ts b/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin.module.ts index 422f1f5461..bea1998754 100644 --- a/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin.module.ts +++ b/apps/commudle-admin/src/app/feature-modules/sys-admin/sys-admin.module.ts @@ -48,6 +48,7 @@ import { AdminFeaturedEventsComponent } from './components/admin-featured/admin- import { AdminFeaturedUsersComponent } from './components/admin-featured/admin-featured-users/admin-featured-users.component'; import { AdminFeaturedCommunitiesChannelsComponent } from './components/admin-featured/admin-featured-communities-channels/admin-featured-communities-channels.component'; import { PaymentLogsComponent } from 'apps/commudle-admin/src/app/feature-modules/sys-admin/components/payment-logs/payment-logs.component'; +import { QuestTaskTypesComponent } from 'apps/commudle-admin/src/app/feature-modules/sys-admin/components/community-controls/quest-task-types/quest-task-types.component'; @NgModule({ declarations: [ SysAdminComponent, @@ -75,6 +76,7 @@ import { PaymentLogsComponent } from 'apps/commudle-admin/src/app/feature-module AdminFeaturedUsersComponent, AdminFeaturedCommunitiesChannelsComponent, PaymentLogsComponent, + QuestTaskTypesComponent, ], imports: [ CommonModule, diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.html b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.html new file mode 100644 index 0000000000..1767f5c33c --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.html @@ -0,0 +1,8 @@ +
+ + Discord Logo +
+ Redirect To: + {{ redirectTo }} +
+
diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.scss b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.scss new file mode 100644 index 0000000000..9ebeb1b170 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.scss @@ -0,0 +1,9 @@ +section { + @apply com-flex com-justify-center com-flex-col com-h-[80dvh] com-gap-14; + img { + @apply com-h-40; + } + div { + @apply com-text-center; + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.spec.ts new file mode 100644 index 0000000000..590fdc8f82 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { DiscordComponent } from './discord.component'; + +describe('DiscordComponent', () => { + let component: DiscordComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [DiscordComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DiscordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.ts new file mode 100644 index 0000000000..976f9e3634 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/discord/discord.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { EExternalUserAuth } from '@commudle/shared-models'; +import { ExternalUserAuthHandlerService } from '@commudle/shared-services'; +import { staticAssets } from 'apps/commudle-admin/src/assets/static-assets'; + +@Component({ + selector: 'commudle-discord', + templateUrl: './discord.component.html', + styleUrls: ['./discord.component.scss'], +}) +export class DiscordComponent implements OnInit { + redirectTo: string; + staticAssets = staticAssets; + constructor( + private activatedRoute: ActivatedRoute, + private externalUserAuthHandlerService: ExternalUserAuthHandlerService, + ) {} + + ngOnInit() { + this.activatedRoute.queryParamMap.subscribe((params) => { + const encodedState = params.get('state'); + const state = JSON.parse(decodeURIComponent(encodedState)); + const baseUrl = state.redirectTo; + this.redirectTo = `${baseUrl}?task_id=${state.task_id}`; + this.externalUserAuthHandlerService.getTokenFromUrl(EExternalUserAuth.DISCORD); + window.location.href = this.redirectTo; + }); + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.html b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.html new file mode 100644 index 0000000000..981c0eb634 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.html @@ -0,0 +1,8 @@ +
+ + Instagram Logo +
+ Redirect To: + {{ redirectTo }} +
+
diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.scss b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.scss new file mode 100644 index 0000000000..9ebeb1b170 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.scss @@ -0,0 +1,9 @@ +section { + @apply com-flex com-justify-center com-flex-col com-h-[80dvh] com-gap-14; + img { + @apply com-h-40; + } + div { + @apply com-text-center; + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.spec.ts new file mode 100644 index 0000000000..dc5af66ca4 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { InstagramComponent } from './instagram.component'; + +describe('InstagramComponent', () => { + let component: InstagramComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [InstagramComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InstagramComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.ts new file mode 100644 index 0000000000..9fa713124f --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/instagram/instagram.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { EExternalUserAuth } from '@commudle/shared-models'; +import { ExternalUserAuthHandlerService } from '@commudle/shared-services'; +import { staticAssets } from 'apps/commudle-admin/src/assets/static-assets'; + +@Component({ + selector: 'commudle-instagram', + templateUrl: './instagram.component.html', + styleUrls: ['./instagram.component.scss'], +}) +export class InstagramComponent implements OnInit { + redirectTo: string; + staticAssets = staticAssets; + constructor( + private activatedRoute: ActivatedRoute, + private externalUserAuthHandlerService: ExternalUserAuthHandlerService, + ) {} + + ngOnInit() { + this.activatedRoute.queryParamMap.subscribe((params) => { + const encodedState = params.get('state'); + const state = JSON.parse(decodeURIComponent(encodedState)); + this.redirectTo = state.redirectTo; + this.externalUserAuthHandlerService.getTokenFromUrl(EExternalUserAuth.INSTAGRAM); + window.location.href = this.redirectTo; + }); + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.html b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.html new file mode 100644 index 0000000000..4db73de8c3 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.html @@ -0,0 +1,8 @@ +
+ + Telegram Logo +
+ Redirect To: + {{ redirectTo }} +
+
diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.scss b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.scss new file mode 100644 index 0000000000..9ebeb1b170 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.scss @@ -0,0 +1,9 @@ +section { + @apply com-flex com-justify-center com-flex-col com-h-[80dvh] com-gap-14; + img { + @apply com-h-40; + } + div { + @apply com-text-center; + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.spec.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.spec.ts new file mode 100644 index 0000000000..6d860fe62b --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { TelegramComponent } from './telegram.component'; + +describe('TelegramComponent', () => { + let component: TelegramComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TelegramComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TelegramComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.ts new file mode 100644 index 0000000000..276a5f6dcb --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/components/telegram/telegram.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { EExternalUserAuth } from '@commudle/shared-models'; +import { ExternalUserAuthHandlerService } from '@commudle/shared-services'; +import { staticAssets } from 'apps/commudle-admin/src/assets/static-assets'; + +@Component({ + selector: 'commudle-telegram', + templateUrl: './telegram.component.html', + styleUrls: ['./telegram.component.scss'], +}) +export class TelegramComponent implements OnInit { + redirectTo: string; + staticAssets = staticAssets; + constructor( + private activatedRoute: ActivatedRoute, + private externalUserAuthHandlerService: ExternalUserAuthHandlerService, + ) {} + + ngOnInit() { + this.activatedRoute.queryParamMap.subscribe((params) => { + const encodedState = params.get('state'); + const state = JSON.parse(decodeURIComponent(encodedState)); + this.redirectTo = state.redirectTo; + this.externalUserAuthHandlerService.getTokenFromUrl(EExternalUserAuth.TELEGRAM); + window.location.href = this.redirectTo; + }); + } +} diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/user-oauth.module.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/user-oauth.module.ts new file mode 100644 index 0000000000..8a2796c808 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/user-oauth.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { UserOauthRoutes } from './user-oauth.routing'; +import { DiscordComponent } from './components/discord/discord.component'; +import { SharedComponentsModule } from '@commudle/shared-components'; +import { InstagramComponent } from './components/instagram/instagram.component'; +import { TelegramComponent } from './components/telegram/telegram.component'; + +@NgModule({ + imports: [CommonModule, UserOauthRoutes, SharedComponentsModule], + declarations: [DiscordComponent, TelegramComponent, InstagramComponent], +}) +export class UserOauthModule {} diff --git a/apps/commudle-admin/src/app/feature-modules/user-oauth/user-oauth.routing.ts b/apps/commudle-admin/src/app/feature-modules/user-oauth/user-oauth.routing.ts new file mode 100644 index 0000000000..1e5fb2ade6 --- /dev/null +++ b/apps/commudle-admin/src/app/feature-modules/user-oauth/user-oauth.routing.ts @@ -0,0 +1,21 @@ +import { Routes, RouterModule } from '@angular/router'; +import { DiscordComponent } from './components/discord/discord.component'; +import { TelegramComponent } from './components/telegram/telegram.component'; +import { InstagramComponent } from './components/instagram/instagram.component'; + +const routes: Routes = [ + { + path: 'discord/callback', + component: DiscordComponent, + }, + { + path: 'instagram/callback', + component: InstagramComponent, + }, + { + path: 'telegram/callback', + component: TelegramComponent, + }, +]; + +export const UserOauthRoutes = RouterModule.forChild(routes); diff --git a/apps/commudle-admin/src/app/services/communities.service.ts b/apps/commudle-admin/src/app/services/communities.service.ts index a7798ceb01..38ef49a297 100644 --- a/apps/commudle-admin/src/app/services/communities.service.ts +++ b/apps/commudle-admin/src/app/services/communities.service.ts @@ -44,7 +44,7 @@ export class CommunitiesService { } // get the details of a community - getCommunityDetails(communityId: number): Observable { + getCommunityDetails(communityId: number | string): Observable { const params = new HttpParams().set('community_id', String(communityId)); return this.http.get(this.apiRoutesService.getRoute(API_ROUTES.COMMUNITIES.DETAILS), { params }); } diff --git a/apps/commudle-admin/src/assets/static-assets.ts b/apps/commudle-admin/src/assets/static-assets.ts index 81ae1ef5d7..0a4a3aa488 100644 --- a/apps/commudle-admin/src/assets/static-assets.ts +++ b/apps/commudle-admin/src/assets/static-assets.ts @@ -178,4 +178,24 @@ export const staticAssets = { //id: 158 call_for_speaker_empty_state: 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBL0N1QWc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--3d0e4b9c60c0f63c0272e41a6ea79608454cc379/CFP-Empty%20State.png', + + // id: 161 + instagram_logo: + 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBeXcvQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--46e0fcd32ed6e38bf0439c81d04bf730fd392b7d/Instagram.svg', + + // id: 162 + discord_logo: + 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBeTAvQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--d01625bde4293ef3b7d8de196a73f603114eed05/Discord.svg', + + // id: 163 + telegram_logo: + 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBeTQvQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--1e096d1892f9cb80c35bc962b86956429681e00a/Telegram.svg', + + // id: 169 + quest_empty_state: + 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBM3RuQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--df86d3126a9f218dec017a1934acfd49a1e4dc56/Group%201.png', + + // id: 170 + quest_task_rewards: + 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBMjluQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--2297c1f6dafa2e70379940b391f20fbae9c982ba/RANK%20BADGES.svg', }; diff --git a/apps/shared-components/quest-card/quest-card.component.html b/apps/shared-components/quest-card/quest-card.component.html new file mode 100644 index 0000000000..ea1c673c64 --- /dev/null +++ b/apps/shared-components/quest-card/quest-card.component.html @@ -0,0 +1,16 @@ + + + + +

{{ quest.name }}

+
+ {{ + quest.total_rewards_points + }} + Reward Points +
+
+
+
diff --git a/apps/shared-components/quest-card/quest-card.component.scss b/apps/shared-components/quest-card/quest-card.component.scss new file mode 100644 index 0000000000..535d1833f9 --- /dev/null +++ b/apps/shared-components/quest-card/quest-card.component.scss @@ -0,0 +1,23 @@ +a { + @apply com-no-underline; + nb-card { + @apply hover:com-shadow-md com-m-0; + nb-card-header { + @apply com-p-0 com-overflow-hidden com-h-full com-border-0; + img { + @apply com-w-full; + } + } + + nb-card-body { + @apply com-min-h-[134px]; + .title { + @apply com-font-medium com-text-lg com-line-clamp-2 com-my-0 com-h-14; + } + + .reward-points { + @apply com-flex com-gap-2 com-items-center com-border com-border-solid com-border-Bright-Gray com-rounded-3xl com-text-orange-700 com-py-1 com-px-3 com-w-max com-mt-4; + } + } + } +} diff --git a/apps/shared-components/quest-card/quest-card.component.spec.ts b/apps/shared-components/quest-card/quest-card.component.spec.ts new file mode 100644 index 0000000000..b9416c9c44 --- /dev/null +++ b/apps/shared-components/quest-card/quest-card.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { QuestCardComponent } from './quest-card.component'; + +describe('QuestCardComponent', () => { + let component: QuestCardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [QuestCardComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/shared-components/quest-card/quest-card.component.ts b/apps/shared-components/quest-card/quest-card.component.ts new file mode 100644 index 0000000000..1a7654c9c4 --- /dev/null +++ b/apps/shared-components/quest-card/quest-card.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { IQuest } from '@commudle/shared-models'; +import { staticAssets } from 'apps/commudle-admin/src/assets/static-assets'; + +@Component({ + selector: 'commudle-quest-card', + templateUrl: './quest-card.component.html', + styleUrls: ['./quest-card.component.scss'], +}) +export class QuestCardComponent implements OnInit { + @Input() quest: IQuest; + staticAssets = staticAssets; + + constructor() {} + + ngOnInit() {} +} diff --git a/apps/shared-components/quest-form/quest-form.component.html b/apps/shared-components/quest-form/quest-form.component.html new file mode 100644 index 0000000000..0d29bc5970 --- /dev/null +++ b/apps/shared-components/quest-form/quest-form.component.html @@ -0,0 +1,70 @@ + + {{ quest ? 'Edit quest' : 'New quest' }} + +
+ +
+ + + + +
+ + +
+ + + + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +
+
+   + +
+
+ +
+
+
+ + + + +
diff --git a/apps/shared-components/quest-form/quest-form.component.scss b/apps/shared-components/quest-form/quest-form.component.scss new file mode 100644 index 0000000000..ed9dcf2104 --- /dev/null +++ b/apps/shared-components/quest-form/quest-form.component.scss @@ -0,0 +1,25 @@ +nb-card { + nb-card-body { + form { + @apply com-flex com-flex-col com-gap-4; + .label-container { + @apply com-flex com-flex-col com-gap-0.5; + label { + @apply com-mb-2; + .required { + @apply com-text-red-500; + } + } + } + .date-container { + @apply com-flex com-gap-6 com-border-0 com-border-b com-border-t com-border-solid com-border-Bright-Gray com-py-3; + .label-container { + @apply com-w-full; + } + } + .radio-buttons { + @apply com-flex com-gap-5; + } + } + } +} diff --git a/apps/shared-components/quest-form/quest-form.component.spec.ts b/apps/shared-components/quest-form/quest-form.component.spec.ts new file mode 100644 index 0000000000..63adfdbe55 --- /dev/null +++ b/apps/shared-components/quest-form/quest-form.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { QuestFormComponent } from './quest-form.component'; + +describe('QuestFormComponent', () => { + let component: QuestFormComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [QuestFormComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/shared-components/quest-form/quest-form.component.ts b/apps/shared-components/quest-form/quest-form.component.ts new file mode 100644 index 0000000000..6d5890ed83 --- /dev/null +++ b/apps/shared-components/quest-form/quest-form.component.ts @@ -0,0 +1,136 @@ +import { DatePipe } from '@angular/common'; +import { Component, Input, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { EDbModels, IQuest, EQuestVisibility } from '@commudle/shared-models'; +import { QuestService, ToastrService } from '@commudle/shared-services'; + +@Component({ + selector: 'commudle-quest-form', + templateUrl: './quest-form.component.html', + styleUrls: ['./quest-form.component.scss'], +}) +export class QuestFormComponent implements OnInit { + @Input() buttonText: string = 'Save'; + @Input() quest: IQuest; + parentId = ''; + parentType: EDbModels; + EQuestVisibility = EQuestVisibility; + questForm: FormGroup; + isLoading = false; + + tinyMCE = { + min_height: 300, + menubar: false, + convert_urls: false, + placeholder: 'Write description for hackathon', + content_style: + "@import url('https://fonts.googleapis.com/css?family=Inter'); body {font-family: 'Inter'; font-size: 16px !important;}", + plugins: [ + 'emoticons', + 'advlist', + 'lists', + 'autolink', + 'link', + 'charmap', + 'preview', + 'anchor', + 'image', + 'visualblocks', + 'code', + 'charmap', + 'codesample', + 'insertdatetime', + 'table', + 'code', + 'help', + 'wordcount', + 'autoresize', + 'media', + ], + toolbar: + 'bold italic backcolor | codesample emoticons | link | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | media code | removeformat | table', + default_link_target: '_blank', + branding: false, + license_key: 'gpl', + }; + + constructor( + private activatedRoute: ActivatedRoute, + private fb: FormBuilder, + private questService: QuestService, + private datePipe: DatePipe, + private toastService: ToastrService, + ) { + this.questForm = this.fb.group({ + name: ['', Validators.required], + description: ['', Validators.required], + start_date: [''], + end_date: [''], + visibility: ['', Validators.required], + }); + } + + ngOnInit() { + if (this.quest) { + this.fetchQuestDetails(); + } + this.activatedRoute.parent.paramMap.subscribe((params) => { + if (params.get('community_id')) { + this.parentId = params.get('community_id'); + this.parentType = EDbModels.KOMMUNITY; + } + if (params.get('community_group_id')) { + this.parentId = params.get('community_group_id'); + this.parentType = EDbModels.COMMUNITY_GROUP; + } + }); + } + + createOrUpdateQuest() { + if (this.quest) { + this.updateQuest(); + } else { + this.createQuest(); + } + } + + createQuest() { + this.isLoading = true; + this.questService.create(this.questForm.value, this.parentType, this.parentId).subscribe( + (quest: IQuest) => { + this.isLoading = false; + if (quest) { + this.toastService.successDialog('Quest created successfully'); + } + }, + () => { + this.isLoading = false; + }, + ); + } + updateQuest() { + this.isLoading = true; + this.questService.update(this.quest.id, this.questForm.value).subscribe( + (quest: IQuest) => { + this.isLoading = false; + if (quest) { + this.toastService.successDialog('Quest updated successfully'); + } + }, + () => { + this.isLoading = false; + }, + ); + } + + fetchQuestDetails() { + this.questForm.patchValue({ + name: this.quest.name, + description: this.quest.description, + start_date: this.datePipe.transform(this.quest.start_date, 'yyyy-MM-ddTHH:mm:ss'), + end_date: this.datePipe.transform(this.quest.end_date, 'yyyy-MM-ddTHH:mm:ss'), + visibility: this.quest.visibility, + }); + } +} diff --git a/apps/shared-components/quest-task-card/quest-task-card.component.html b/apps/shared-components/quest-task-card/quest-task-card.component.html new file mode 100644 index 0000000000..d9304ea3e4 --- /dev/null +++ b/apps/shared-components/quest-task-card/quest-task-card.component.html @@ -0,0 +1,86 @@ + + +
+ +

{{ task.name }}

+
+

{{ task.description }}

+
+ + {{ task.reward_points }} Reward Points +
+ +
+ + Completed +
+
+
+
+ + + + +
{{ data.name }}
+ +
+ +
+

Connect your account

+

+ Connect your {{ data.quest_task_type.platform_name | titlecase }} account to access the community server +

+ +
+ +
+
+ +
+ +
+ +
+ +
+ + +

+ {{ data.quest_task_type.platform_type | capitalizeAndRemoveUnderscore }} on + {{ data.quest_task_type.platform_name | titlecase }} +

+ +
+
+ + +

Verify on Commudle

+ +
+
+
+
+
+
diff --git a/apps/shared-components/quest-task-card/quest-task-card.component.scss b/apps/shared-components/quest-task-card/quest-task-card.component.scss new file mode 100644 index 0000000000..caee264581 --- /dev/null +++ b/apps/shared-components/quest-task-card/quest-task-card.component.scss @@ -0,0 +1,78 @@ +.task { + @apply com-cursor-pointer hover:com-shadow; + .title-container { + @apply com-flex com-gap-2 com-items-center; + img { + @apply com-h-6; + } + .title { + @apply com-text-base com-font-medium com-my-0; + } + } + .description { + @apply com-h-10 com-line-clamp-2; + } + .rewards { + @apply com-flex com-items-center com-gap-2 com-w-full com-border com-border-solid com-border-Bright-Gray com-rounded-lg com-bg-gray-50 com-text-fuchsia-600 com-py-1 com-justify-center; + } + + .task-completed { + @apply com-flex com-items-center com-justify-center com-gap-2 com-w-full com-border com-border-solid com-border-green-600 com-bg-green-50 com-text-green-600 com-rounded-lg com-py-1; + span { + @apply com-text-sm com-font-bold; + } + } + + &.admin-properties { + @apply com-cursor-text com-pointer-events-none hover:com-shadow-none; + } +} + +.discord-dialog { + @apply com-w-full md:com-w-[400px]; + nb-card-header, + nb-card-footer { + @apply com-flex com-justify-between com-items-center; + } + + .step-one { + @apply com-flex com-flex-col com-items-center; + .title { + @apply com-text-Yankees-Blue com-font-semibold com-text-base com-my-0; + } + .sub-title { + @apply com-text-sm com-text-gray-500 com-text-center com-mt-2 com-mb-5; + } + } + + .step-two { + @apply com-flex com-flex-col com-items-center; + .lock-icon { + @apply com-h-12 com-w-12 com-border com-border-solid com-border-Bright-Gray com-rounded-lg com-flex com-justify-center com-items-center com-mb-2; + fa-icon { + @apply com-text-3xl; + } + } + .title { + @apply com-text-Yankees-Blue com-font-semibold com-text-base com-my-0; + } + .sub-title { + @apply com-text-sm com-text-gray-500 com-text-center com-mt-2; + } + .select { + @apply com-flex com-items-center com-gap-1 com-h-8 com-p-1 com-text-sm com-rounded-[4px] com-cursor-pointer com-bg-[#F6F9FC] hover:com-bg-[#eef2f7]/50 com-border com-border-solid com-border-[#E4E9F2] com-w-full com-mb-5; + select { + @apply com-bg-[#F6F9FC] hover:com-bg-[#eef2f7]/50 com-cursor-pointer com-border-0 com-w-full; + } + } + } + + .step-three { + nb-card { + @apply com-mx-0 com-mt-0; + .title { + @apply com-font-bold com-text-sm com-mb-3 com-mt-0; + } + } + } +} diff --git a/apps/shared-components/quest-task-card/quest-task-card.component.spec.ts b/apps/shared-components/quest-task-card/quest-task-card.component.spec.ts new file mode 100644 index 0000000000..f7b2449ac0 --- /dev/null +++ b/apps/shared-components/quest-task-card/quest-task-card.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { QuestTaskCardComponent } from './quest-task-card.component'; + +describe('QuestTaskCardComponent', () => { + let component: QuestTaskCardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [QuestTaskCardComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestTaskCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/shared-components/quest-task-card/quest-task-card.component.ts b/apps/shared-components/quest-task-card/quest-task-card.component.ts new file mode 100644 index 0000000000..80bb5d0e9c --- /dev/null +++ b/apps/shared-components/quest-task-card/quest-task-card.component.ts @@ -0,0 +1,169 @@ +import { Component, EventEmitter, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core'; +import { + EExternalUserAuth, + IExternalUserAuth, + ITask, + ERewardPointsColors, + ERewardPointsLogo, + IQuestUserResponse, + IQuestTaskUserResponse, +} from '@commudle/shared-models'; +import { + ExternalUserAuthHandlerService, + ExternalUserAuthService, + TaskService, + ToastrService, + QuestUserResponsesService, +} from '@commudle/shared-services'; +import { NbDialogRef, NbDialogService } from '@commudle/theme'; +import { faCircleCheck, faLock } from '@fortawesome/free-solid-svg-icons'; + +@Component({ + selector: 'commudle-quest-task-card', + templateUrl: './quest-task-card.component.html', + styleUrls: ['./quest-task-card.component.scss'], +}) +export class QuestTaskCardComponent implements OnInit { + @Input() task: ITask; + @Input() adminPage: boolean = false; + @Input() questUserResponse: IQuestUserResponse; + @Output() updateUserResponse = new EventEmitter(); + + userAuthTokens: IExternalUserAuth[]; + selectedExternalUserAuth: IExternalUserAuth; + stepOne = true; + stepTwo = false; + stepThree = false; + ERewardPointsColors = ERewardPointsColors; + ERewardPointsLogo = ERewardPointsLogo; + icons = { + faLock, + faCircleCheck, + }; + dialogRef: NbDialogRef; + isVerifying = false; + @ViewChild('discordDialog') discordDialogBox: TemplateRef; + + constructor( + private dialogBoxService: NbDialogService, + private externalUserAuthHandlerService: ExternalUserAuthHandlerService, + private externalUserAuthService: ExternalUserAuthService, + private taskService: TaskService, + private toasterService: ToastrService, + private questUserResponsesService: QuestUserResponsesService, + ) {} + + ngOnInit() {} + + openTaskPopup(task) { + if (this.questUserResponse) { + const taskResponseExist = this.questUserResponse.task_responses.find( + (userTaskResponse) => userTaskResponse.task_id === task.id, + ); + if (!taskResponseExist) { + this.createQuestTaskUserResponse(this.questUserResponse.id); + } + } else { + this.createQuestUserResponse(); + } + this.stepOne = true; + this.stepTwo = false; + this.stepThree = false; + + switch (task.quest_task_type.platform_name) { + case EExternalUserAuth.DISCORD: + this.getExternalUsers(EExternalUserAuth.DISCORD); + this.dialogRef = this.dialogBoxService.open(this.discordDialogBox, { context: task }); + return; + case EExternalUserAuth.INSTAGRAM: + this.getExternalUsers(EExternalUserAuth.INSTAGRAM); + this.dialogRef = this.dialogBoxService.open(this.discordDialogBox, { context: task }); + return; + case EExternalUserAuth.TELEGRAM: + this.getExternalUsers(EExternalUserAuth.TELEGRAM); + this.dialogRef = this.dialogBoxService.open(this.discordDialogBox, { context: task }); + return; + } + } + + getExternalUsers(platformName) { + this.externalUserAuthService.userAuthTokenIndex(platformName).subscribe((res) => { + this.userAuthTokens = res; + if (this.userAuthTokens.length > 0) { + this.stepOne = false; + this.stepTwo = true; + this.selectedExternalUserAuth = this.userAuthTokens[0]; + } + }); + } + + selectUserAuthToken(event) { + const selected = this.userAuthTokens.find((token) => token.id === event.target.value); + this.selectedExternalUserAuth = selected; + } + + loginAccount() { + switch (this.task.quest_task_type.platform_name) { + case EExternalUserAuth.DISCORD: + this.externalUserAuthHandlerService.loginToDiscord(this.task.id); + return; + case EExternalUserAuth.INSTAGRAM: + this.externalUserAuthHandlerService.loginToInstagram(); + return; + case EExternalUserAuth.TELEGRAM: + this.externalUserAuthHandlerService.loginToTelegram(); + return; + } + } + + verifyDiscordServer() { + this.isVerifying = true; + this.taskService.verifyDiscord(this.task.id, this.selectedExternalUserAuth.id).subscribe((res) => { + if (res) { + setTimeout(() => { + const index = this.questUserResponse.task_responses.findIndex( + (userTaskResponse) => userTaskResponse.task_id === this.task.id, + ); + this.questUserResponsesService + .questTaskUserResponseMarkedCompleted(this.questUserResponse.task_responses[index].id) + .subscribe((data) => { + this.questUserResponse.task_responses[index] = data; + this.updateUserResponse.emit(true); + }); + this.toasterService.successDialog('Verified!, You have completed this task'); + this.dialogRef.close(); + this.isVerifying = false; + }, 1000); + } else { + this.isVerifying = false; + this.toasterService.warningDialog('Not Verified'); + } + }); + } + + createQuestUserResponse() { + this.questUserResponsesService.create(this.task.quest_id).subscribe((data) => { + this.questUserResponse = data; + this.createQuestTaskUserResponse(data.id); + }); + } + + createQuestTaskUserResponse(questUserResponseId) { + this.questUserResponsesService + .create_quest_task_user_response(this.task.id, questUserResponseId) + .subscribe((res) => { + this.questUserResponse.task_responses.unshift(res); + }); + } + + taskCompleted(): boolean { + if (!this.questUserResponse || !this.questUserResponse.task_responses) { + return false; + } + + return this.questUserResponse.task_responses.some( + (taskResponse: { completed: boolean; task_id: number }) => + taskResponse.completed === true && taskResponse.task_id === this.task.id, + ); + } +} diff --git a/apps/shared-components/shared-components.module.ts b/apps/shared-components/shared-components.module.ts index 13dc46ba7e..b06231fbaa 100644 --- a/apps/shared-components/shared-components.module.ts +++ b/apps/shared-components/shared-components.module.ts @@ -28,6 +28,7 @@ import { NbToggleModule, NbTooltipModule, NbWindowModule, + NbDialogModule, } from '@commudle/theme'; import { PickerModule } from '@ctrl/ngx-emoji-mart'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; @@ -91,7 +92,9 @@ import { HelpSectionComponent } from 'apps/commudle-admin/src/app/app-shared-com import { UserDetailsCheckboxFormComponent } from './user-details-checkbox-form/user-details-checkbox-form.component'; import { UserDetailsFormComponent } from './user-details-form/user-details-form.component'; import { NewsletterCardComponent } from './newsletter-card/newsletter-card.component'; - +import { QuestFormComponent } from './quest-form/quest-form.component'; +import { QuestTaskCardComponent } from './quest-task-card/quest-task-card.component'; +import { QuestCardComponent } from './quest-card/quest-card.component'; @NgModule({ declarations: [ WorkInProgressComponent, @@ -145,6 +148,9 @@ import { NewsletterCardComponent } from './newsletter-card/newsletter-card.compo UserDetailsCheckboxFormComponent, UserDetailsFormComponent, NewsletterCardComponent, + QuestFormComponent, + QuestTaskCardComponent, + QuestCardComponent, ], imports: [ CommonModule, @@ -188,6 +194,7 @@ import { NewsletterCardComponent } from './newsletter-card/newsletter-card.compo NbToggleModule, NbContextMenuModule, NbSpinnerModule, + NbDialogModule, //cdk DragDropModule, @@ -236,6 +243,9 @@ import { NewsletterCardComponent } from './newsletter-card/newsletter-card.compo UserDetailsCheckboxFormComponent, UserDetailsFormComponent, NewsletterCardComponent, + QuestFormComponent, + QuestTaskCardComponent, + QuestCardComponent, ], providers: [{ provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }], }) diff --git a/libs/shared/environments/src/lib/environments.ts b/libs/shared/environments/src/lib/environments.ts index 8a0e33f84b..0c3a7216f3 100644 --- a/libs/shared/environments/src/lib/environments.ts +++ b/libs/shared/environments/src/lib/environments.ts @@ -12,6 +12,7 @@ type Environment = { stripe: string; sentry_dsn: string; razorpay_key: string; + discord_client_id: string; }; const environments: { [type: string]: Environment } = { @@ -30,6 +31,7 @@ const environments: { [type: string]: Environment } = { 'pk_live_51NIQahSAaAm97Wzm6Nthh2SsbFH123ckuvoO9P4fEghH2IeC5laeiJmYSBHbOt9bFE1fHY1Hwig5lqiHko0bn7Yi00EWLt1AuB', sentry_dsn: 'https://008ea5f833883ac6e933856b26757b7e@o566989.ingest.sentry.io/4506098297405440', razorpay_key: 'rzp_test_AQ8emxZcsJoKdl', + discord_client_id: '1298505458629214218', }, test: { production: false, @@ -46,6 +48,7 @@ const environments: { [type: string]: Environment } = { 'pk_test_51NIQahSAaAm97WzmtpZtqYAuI1cCfN7LAJPoy8SmBpJqXQ5c7gnmOXXS9VtXa1b6YvCa1Uc9bX3Ra9ZLjm4AQBSs00en3kVojH', sentry_dsn: 'https://008ea5f833883ac6e933856b26757b7e@o566989.ingest.sentry.io/4506098297405440', razorpay_key: 'rzp_test_AQ8emxZcsJoKdl', + discord_client_id: '1298505458629214218', }, staging: { production: false, @@ -62,6 +65,7 @@ const environments: { [type: string]: Environment } = { 'pk_live_51NIQahSAaAm97Wzm6Nthh2SsbFH123ckuvoO9P4fEghH2IeC5laeiJmYSBHbOt9bFE1fHY1Hwig5lqiHko0bn7Yi00EWLt1AuB', sentry_dsn: 'https://008ea5f833883ac6e933856b26757b7e@o566989.ingest.sentry.io/4506098297405440', razorpay_key: 'rzp_live_nqGSJl7Jt6bsZx', + discord_client_id: '1298505458629214218', }, production: { production: true, @@ -78,6 +82,7 @@ const environments: { [type: string]: Environment } = { 'pk_live_51NIQahSAaAm97Wzm6Nthh2SsbFH123ckuvoO9P4fEghH2IeC5laeiJmYSBHbOt9bFE1fHY1Hwig5lqiHko0bn7Yi00EWLt1AuB', sentry_dsn: 'https://008ea5f833883ac6e933856b26757b7e@o566989.ingest.sentry.io/4506098297405440', razorpay_key: 'rzp_live_nqGSJl7Jt6bsZx', + discord_client_id: '1298505458629214218', }, }; diff --git a/libs/shared/models/src/index.ts b/libs/shared/models/src/index.ts index 33ae60fb91..cc9ce7ce75 100644 --- a/libs/shared/models/src/index.ts +++ b/libs/shared/models/src/index.ts @@ -40,3 +40,9 @@ export * from './lib/help-dictionary.model'; export * from './lib/upcoming-event-hackathon.model'; export * from './lib/hackathon-user-response.model'; export * from './lib/activity-feed.model'; +export * from './lib/external-user-auth.model'; +export * from './lib/quest.model'; +export * from './lib/task.model'; +export * from './lib/quest-task-type.model'; +export * from './lib/quest-user-response.model'; +export * from './lib/quest-task-user-response.model'; diff --git a/libs/shared/models/src/lib/external-user-auth.model.ts b/libs/shared/models/src/lib/external-user-auth.model.ts new file mode 100644 index 0000000000..f2d7b90256 --- /dev/null +++ b/libs/shared/models/src/lib/external-user-auth.model.ts @@ -0,0 +1,19 @@ +export interface IExternalUserAuth { + id: number; + platform: EExternalUserAuth; + token: string; + active: boolean; + data: Date; + username: string; +} + +export enum EExternalUserAuth { + DISCORD = 'discord', + TWITTER = 'twitter', + INSTAGRAM = 'instagram', + FACEBOOK = 'facebook', + LINKEDIN = 'linkedin', + TELEGRAM = 'telegram', + YOUTUBE = 'youtube', + GITHUB = 'github', +} diff --git a/libs/shared/models/src/lib/quest-task-type.model.ts b/libs/shared/models/src/lib/quest-task-type.model.ts new file mode 100644 index 0000000000..acc7ea3fab --- /dev/null +++ b/libs/shared/models/src/lib/quest-task-type.model.ts @@ -0,0 +1,13 @@ +export interface IQuestTaskType { + id: number; + platform_name: string; + logo_url: string; + description: string; + platform_type: string; + platform_types: [ + { + id: number; + name: string; + }, + ]; +} diff --git a/libs/shared/models/src/lib/quest-task-user-response.model.ts b/libs/shared/models/src/lib/quest-task-user-response.model.ts new file mode 100644 index 0000000000..2ccf61130d --- /dev/null +++ b/libs/shared/models/src/lib/quest-task-user-response.model.ts @@ -0,0 +1,7 @@ +export interface IQuestTaskUserResponse { + id: number; + completed: boolean; + task_id: number; + quest_user_response_id: number; + user_id: number; +} diff --git a/libs/shared/models/src/lib/quest-user-response.model.ts b/libs/shared/models/src/lib/quest-user-response.model.ts new file mode 100644 index 0000000000..3d1104fe66 --- /dev/null +++ b/libs/shared/models/src/lib/quest-user-response.model.ts @@ -0,0 +1,11 @@ +import { IUser } from './user.model'; +import { IQuestTaskUserResponse } from './quest-task-user-response.model'; + +export interface IQuestUserResponse { + id: number; + completed_task_count: number; + reward_points_earned: number; + task_responses: IQuestTaskUserResponse[]; + user: IUser; + last_task_time: Date; +} diff --git a/libs/shared/models/src/lib/quest.model.ts b/libs/shared/models/src/lib/quest.model.ts new file mode 100644 index 0000000000..0849f49b7e --- /dev/null +++ b/libs/shared/models/src/lib/quest.model.ts @@ -0,0 +1,23 @@ +import { ICommunity } from './community.model'; + +export interface IQuest { + id: number; + name: string; + description: string; + start_date: Date; + end_date: Date; + visibility: EQuestVisibility; + created_at: Date; + slug: string; + tasks_count: number; + participants_count: number; + community: ICommunity; + is_active: boolean; + total_rewards_points: number; +} + +export enum EQuestVisibility { + PUBLIC = 'is_public', + PRIVATE = 'is_private', + DRAFT = 'is_draft', +} diff --git a/libs/shared/models/src/lib/task.model.ts b/libs/shared/models/src/lib/task.model.ts new file mode 100644 index 0000000000..6bfe1a429c --- /dev/null +++ b/libs/shared/models/src/lib/task.model.ts @@ -0,0 +1,28 @@ +import { IQuestTaskType } from './quest-task-type.model'; + +export interface ITask { + id: number; + name: string; + description: string; + reward_points: number; + other_rewards: string; + url: string; + quest_task_type: IQuestTaskType; + quest_id: number; +} + +export const ERewardPointsColors = { + 1: 'com-text-yellow-700', + 2: 'com-text-slate-500', + 3: 'com-text-yellow-600', + 4: 'com-text-blue-500', + 5: 'com-text-fuchsia-600', +}; + +export const ERewardPointsLogo = { + 1: 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBOXBhQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--9b2974c3f3b141442f7a4385a2844074bdd8cbac/1.svg', + 2: 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBOXRhQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--9893d9a6abbba504f0ee8552d9a9014b3ca753b8/2.svg', + 3: 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBOXhhQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--038531b272cf14887b398b734da6d046fe5456d2/3.svg', + 4: 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBOTFhQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--5d1935fdb21c170d2239ff2f883bf75bc2e8aa21/4.svg', + 5: 'https://json.commudle.com/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBOTVhQXc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--af928159fd59e68004390eb9f73ca3b1755fa84b/5.svg', +}; diff --git a/libs/shared/services/src/index.ts b/libs/shared/services/src/index.ts index f3d1757834..bd7859e489 100644 --- a/libs/shared/services/src/index.ts +++ b/libs/shared/services/src/index.ts @@ -22,3 +22,8 @@ export * from './lib/round.service'; export * from './lib/note.service'; export * from './lib/razorpay.service'; export * from './lib/help-dictionary.store'; +export * from './lib/external-user-auth-handler.service'; +export * from './lib/external-user-auth.service'; +export * from './lib/quest.service'; +export * from './lib/task.service'; +export * from './lib/quest-user-responses.service'; diff --git a/libs/shared/services/src/lib/api-routes.constant.ts b/libs/shared/services/src/lib/api-routes.constant.ts index 787c2e01c1..54ed773127 100644 --- a/libs/shared/services/src/lib/api-routes.constant.ts +++ b/libs/shared/services/src/lib/api-routes.constant.ts @@ -390,9 +390,11 @@ export const API_ROUTES = { DELETE: 'api/v2/badges', }, STATIC_ASSETS: { - SHOW: 'api/v2/static_assets', + INDEX: 'api/v2/static_assets', CREATE: 'api/v2/static_assets', + SHOW: 'api/v2/static_assets/show', }, + USER_BADGES: { INDEX: 'api/v2/user_badges', CREATE: 'api/v2/user_badges', @@ -951,4 +953,51 @@ export const API_ROUTES = { INDEX_UPCOMING_HACKATHONS_EVENTS: 'api/v2/feed/events_hackathons/upcoming', ACTIVITY_FEED: 'api/v2/activity_feed', }, + + EXTERNAL_USER_AUTH: { + CREATE: 'api/v2/external_user_auth', //POST + INDEX: 'api/v2/external_user_auth', //GET + }, + + QUESTS: { + CREATE: 'api/v2/quests', //POST + INDEX: 'api/v2/quests', //GET + SHOW: 'api/v2/quests/show', //GET + UPDATE: 'api/v2/quests', //PUT + PUBLIC: { + INDEX: 'api/v2/quests/public', //GET + SHOW: 'api/v2/quests/public/show', //GET + }, + }, + + TASKS: { + CREATE: 'api/v2/tasks', //POST + INDEX: 'api/v2/tasks', //GET + SHOW: 'api/v2/tasks/show', //GET + UPDATE: 'api/v2/tasks', //PUT + TASK_TYPES_INDEX: 'api/v2/tasks/task_types_index', //GET + VERIFY_DISCORD: 'api/v2/tasks/verify_discord', //GET + PUBLIC: { + INDEX: 'api/v2/tasks/public', //GET + SHOW: 'api/v2/tasks/public/show', //GET + }, + QUEST_TASK_TYPE: { + INDEX: 'api/v2/tasks/quest_task_type', //GET + UPDATE: 'api/v2/tasks/quest_task_type', //PUT + CREATE: 'api/v2/tasks/quest_task_type', //POST + DESTROY: 'api/v2/tasks/quest_task_type', //DELETE + }, + }, + + QUEST_USER_RESPONSES: { + CREATE: 'api/v2/quest_user_responses', //POST + INDEX: 'api/v2/quest_user_responses', //GET + CURRENT_USER_RESPONSES: 'api/v2/quest_user_responses/current_user_responses', //GET + CREATE_QUEST_TASK_USER_RESPONSE: 'api/v2/quest_user_responses/create_quest_task_user_response', //POST + QUEST_TASK_USER_RESPONSE_MARKED_COMPLETED: 'api/v2/quest_user_responses/quest_task_user_response_marked_completed', //PUT + PUBLIC: { + INDEX: 'api/v2/quest_user_responses/public', //GET + SHOW: 'api/v2/quest_user_responses/public/show', //GET + }, + }, }; diff --git a/libs/shared/services/src/lib/external-user-auth-handler.service.ts b/libs/shared/services/src/lib/external-user-auth-handler.service.ts new file mode 100644 index 0000000000..ac685fab81 --- /dev/null +++ b/libs/shared/services/src/lib/external-user-auth-handler.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ExternalUserAuthService } from './external-user-auth.service'; +import { environment } from '@commudle/shared-environments'; +import { EExternalUserAuth } from '@commudle/shared-models'; + +@Injectable({ + providedIn: 'root', +}) +export class ExternalUserAuthHandlerService { + constructor(private activatedRoute: ActivatedRoute, private externalUserAuthService: ExternalUserAuthService) {} + + loginToDiscord(task_id?: number): void { + const state = { + redirectTo: window.location.href, + }; + const redirectUrl = environment.app_url + '/auth/users/discord/callback'; + if (task_id) { + state['task_id'] = task_id; + } + const encodedState = encodeURIComponent(JSON.stringify(state)); + const url = `https://discord.com/oauth2/authorize?client_id=${environment.discord_client_id}&response_type=code&redirect_uri=${redirectUrl}&scope=identify+email+guilds+guilds.members.read&state=${encodedState}`; + window.location.href = url; + } + + loginToInstagram() { + const YOUR_INSTAGRAM_CLIENT_ID = '1090108432225078'; + const redirectUrl = environment.app_url + '/auth/users/instagram/callback'; + const state = { + redirectTo: window.location.href, + }; + const encodedState = encodeURIComponent(JSON.stringify(state)); + const instagramLoginUrl = `https://api.instagram.com/oauth/authorize?client_id=${YOUR_INSTAGRAM_CLIENT_ID}&redirect_uri=${redirectUrl}&scope=user_profile,user_media&response_type=code&state=${encodedState}`; + window.location.href = instagramLoginUrl; + } + + loginToTelegram() { + const YOUR_BOT_USERNAME = 'GroupixArshbot'; + const state = { + redirectTo: window.location.href, + }; + const encodedState = encodeURIComponent(JSON.stringify(state)); + const redirectUrl = `https://telegram.me/${YOUR_BOT_USERNAME}?start=auth&state=${encodedState}`; + window.location.href = redirectUrl; + } + // Extract token from URL after Discord login + getTokenFromUrl(platformName: EExternalUserAuth) { + this.activatedRoute.queryParamMap.subscribe((params) => { + const code = params.get('code'); // Retrieve the 'code' parameter + if (code) { + const external_user_auth = { + token: code, + platform: platformName, + }; + this.externalUserAuthService.create(external_user_auth).subscribe(); + } + }); + } +} diff --git a/libs/shared/services/src/lib/external-user-auth.service.ts b/libs/shared/services/src/lib/external-user-auth.service.ts new file mode 100644 index 0000000000..5adb425c02 --- /dev/null +++ b/libs/shared/services/src/lib/external-user-auth.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { API_ROUTES } from './api-routes.constant'; +import { BaseApiService } from './base-api.service'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { IExternalUserAuth } from '@commudle/shared-models'; +@Injectable({ + providedIn: 'root', +}) +export class ExternalUserAuthService { + constructor(private http: HttpClient, private baseApiService: BaseApiService) {} + + create(data): Observable { + return this.http.post(this.baseApiService.getRoute(API_ROUTES.EXTERNAL_USER_AUTH.CREATE), { + external_user_auth: data, + }); + } + + userAuthTokenIndex(platformName: string): Observable { + const params = new HttpParams().set('platform_name', platformName); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.EXTERNAL_USER_AUTH.CREATE), { + params, + }); + } + + verifyDiscordServerMember(externalUserAuthTokenId: number, taskId: number): Observable { + const params = new HttpParams().set('external_user_auth_token_id', externalUserAuthTokenId).set('task_id', taskId); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.EXTERNAL_USER_AUTH.CREATE), { + params, + }); + } +} diff --git a/libs/shared/services/src/lib/quest-user-responses.service.ts b/libs/shared/services/src/lib/quest-user-responses.service.ts new file mode 100644 index 0000000000..c817443c52 --- /dev/null +++ b/libs/shared/services/src/lib/quest-user-responses.service.ts @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { API_ROUTES } from './api-routes.constant'; +import { BaseApiService } from './base-api.service'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { IPaginationCount, IQuestTaskUserResponse, IQuestUserResponse } from '@commudle/shared-models'; +@Injectable({ + providedIn: 'root', +}) +export class QuestUserResponsesService { + constructor(private http: HttpClient, private baseApiService: BaseApiService) {} + + create(questId: number | string): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.post( + this.baseApiService.getRoute(API_ROUTES.QUEST_USER_RESPONSES.CREATE), + params, + ); + } + + index(questId: number | string): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.QUEST_USER_RESPONSES.INDEX), { + params, + }); + } + + getCurrentUserResponses(questId: number | string): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.get( + this.baseApiService.getRoute(API_ROUTES.QUEST_USER_RESPONSES.CURRENT_USER_RESPONSES), + { + params, + }, + ); + } + + create_quest_task_user_response(taskId: number, questUserResponseId: number): Observable { + const params = new HttpParams().set('task_id', taskId).set('quest_user_response_id', questUserResponseId); + return this.http.post( + this.baseApiService.getRoute(API_ROUTES.QUEST_USER_RESPONSES.CREATE_QUEST_TASK_USER_RESPONSE), + params, + ); + } + + questTaskUserResponseMarkedCompleted(taskUserResponseId: number): Observable { + const params = new HttpParams().set('task_user_response_id', taskUserResponseId); + return this.http.put( + this.baseApiService.getRoute(API_ROUTES.QUEST_USER_RESPONSES.QUEST_TASK_USER_RESPONSE_MARKED_COMPLETED), + params, + ); + } + + // PUBLIC API + publicIndex(questId: number | string, page = 1, count = 10): Observable> { + const params = new HttpParams().set('quest_id', questId).set('page', page).set('count', count); + return this.http.get>( + this.baseApiService.getRoute(API_ROUTES.QUEST_USER_RESPONSES.PUBLIC.INDEX), + { + params, + }, + ); + } +} diff --git a/libs/shared/services/src/lib/quest.service.ts b/libs/shared/services/src/lib/quest.service.ts new file mode 100644 index 0000000000..16f378f4eb --- /dev/null +++ b/libs/shared/services/src/lib/quest.service.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { API_ROUTES } from './api-routes.constant'; +import { BaseApiService } from './base-api.service'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { EDbModels, IPaginationCount, IQuest } from '@commudle/shared-models'; +@Injectable({ + providedIn: 'root', +}) +export class QuestService { + constructor(private http: HttpClient, private baseApiService: BaseApiService) {} + + create(formData: IQuest, parentType: EDbModels, parentId: number | string): Observable { + const params = new HttpParams().set('parent_id', parentId).set('parent_type', parentType); + return this.http.post( + this.baseApiService.getRoute(API_ROUTES.QUESTS.CREATE), + { + quest: formData, + }, + { + params, + }, + ); + } + + update(questId: number, data: IQuest): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.put( + this.baseApiService.getRoute(API_ROUTES.QUESTS.UPDATE), + { + quest: data, + }, + { params }, + ); + } + + index(parentType: EDbModels, parentId: number | string, page = 1, count = 10): Observable> { + let params = new HttpParams().set('parent_id', parentId).set('parent_type', parentType); + if (page) { + params = params.set('page', page); + } + if (count) { + params = params.set('count', count); + } + return this.http.get>(this.baseApiService.getRoute(API_ROUTES.QUESTS.INDEX), { + params, + }); + } + + getQuest(questId: number | string): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.QUESTS.SHOW), { + params, + }); + } + + // PUBLIC API + publicIndex( + parentType: EDbModels, + parentId: number | string, + page = 1, + count = 10, + ): Observable> { + let params = new HttpParams().set('parent_id', parentId).set('parent_type', parentType); + if (page) { + params = params.set('page', page); + } + if (count) { + params = params.set('count', count); + } + return this.http.get>(this.baseApiService.getRoute(API_ROUTES.QUESTS.PUBLIC.INDEX), { + params, + }); + } + + publicShow(questId: number): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.QUESTS.PUBLIC.SHOW), { + params, + }); + } +} diff --git a/libs/shared/services/src/lib/task.service.ts b/libs/shared/services/src/lib/task.service.ts new file mode 100644 index 0000000000..6f452512c4 --- /dev/null +++ b/libs/shared/services/src/lib/task.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { API_ROUTES } from './api-routes.constant'; +import { BaseApiService } from './base-api.service'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { IQuestTaskType, ITask } from '@commudle/shared-models'; +@Injectable({ + providedIn: 'root', +}) +export class TaskService { + constructor(private http: HttpClient, private baseApiService: BaseApiService) {} + + createTask(questId: number, formData): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.post( + this.baseApiService.getRoute(API_ROUTES.TASKS.CREATE), + { + task: formData, + }, + { + params, + }, + ); + } + + index(questId: number): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.TASKS.INDEX), { + params, + }); + } + + getTaskTypesIndex(): Observable { + return this.http.get(this.baseApiService.getRoute(API_ROUTES.TASKS.TASK_TYPES_INDEX)); + } + + verifyDiscord(taskId: number, externalUserAuthTokenId: number): Observable { + const params = new HttpParams().set('task_id', taskId).set('external_user_auth_token_id', externalUserAuthTokenId); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.TASKS.VERIFY_DISCORD), { params }); + } + + // PUBLIC API + pIndex(questId: number): Observable { + const params = new HttpParams().set('quest_id', questId); + return this.http.get(this.baseApiService.getRoute(API_ROUTES.TASKS.PUBLIC.INDEX), { + params, + }); + } + + // Quest Task Type + getQuestTaskTypeIndex(): Observable { + return this.http.get(this.baseApiService.getRoute(API_ROUTES.TASKS.QUEST_TASK_TYPE.INDEX)); + } + + createQuestTaskType(formData): Observable { + return this.http.post(this.baseApiService.getRoute(API_ROUTES.TASKS.QUEST_TASK_TYPE.CREATE), { + quest_task_type: formData, + }); + } + + destroyQuestTaskType(questTaskTypeId): Observable { + const params = new HttpParams().set('quest_task_type_id', questTaskTypeId); + return this.http.delete(this.baseApiService.getRoute(API_ROUTES.TASKS.QUEST_TASK_TYPE.DESTROY), { + params, + }); + } + + updateQuestTaskType(questTaskTypeId, formData): Observable { + const params = new HttpParams().set('quest_task_type_id', questTaskTypeId); + return this.http.put( + this.baseApiService.getRoute(API_ROUTES.TASKS.QUEST_TASK_TYPE.UPDATE), + { + quest_task_type: formData, + }, + { + params, + }, + ); + } +}