Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kuwait Theme: Activity Check-in component #2562

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/components/plh/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { PlhParentPointCounterComponent } from "./parent-point-counter/parent-point-counter.component";
import { PlhParentPointBoxComponent } from "./parent-point-box/parent-point-box.component";
import { PlhModuleListItemComponent } from "./plh-kids-kw/components/module-list-item/module-list-item.component";
import { PlhActivityCheckInComponent } from "./plh-kids-kw/components/activity-check-in/activity-check-in.component";

export { PlhParentPointCounterComponent, PlhParentPointBoxComponent, PlhModuleListItemComponent };
export {
PlhParentPointCounterComponent,
PlhParentPointBoxComponent,
PlhModuleListItemComponent,
PlhActivityCheckInComponent,
};

export const PLH_COMPONENTS = [
PlhParentPointCounterComponent,
PlhParentPointBoxComponent,
PlhModuleListItemComponent,
PlhActivityCheckInComponent,
];

export const PLH_COMPONENT_MAPPING = {
parent_point_counter: PlhParentPointCounterComponent,
parent_point_box: PlhParentPointBoxComponent,
plh_module_list_item: PlhModuleListItemComponent,
plh_activity_check_in: PlhActivityCheckInComponent,
};

export type PLHComponentName = keyof typeof PLH_COMPONENT_MAPPING;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@if (daysLeft >= 1) {
<div
class="activity-container"
[attr.data-language-direction]="templateTranslateService.languageDirection()"
>
<div class="wrapper">
<div class="progress-tracker">
<div class="wrapper">
<div class="progress-bar-container">
<div
class="progress-bar"
[ngStyle]="{
width: progressPercentage + '%'
}"
></div>
</div>
</div>
<div class="countdown-text">In {{ daysLeft }} day{{ daysLeft > 1 ? "s" : "" }}</div>
</div>

<div class="image">
<img [src]="params.lockedImageAsset | plhAsset" />
</div>
<div class="details">
<div class="title">
<h4>{{ params.title }}</h4>
</div>
<div class="icon">
<img [src]="params.lockedIconAsset | plhAsset" />
</div>
</div>
</div>
</div>
} @else {
<div class="activity-container">
<div class="wrapper unlocked">
<div class="image">
<img [src]="params.unlockedImageAsset | plhAsset" />
</div>
<div class="details">
<div class="title">
<h4>{{ params.title }}</h4>
</div>
<div class="icon">
<img [src]="params.unlockedIconAsset | plhAsset" />
</div>
</div>
</div>
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { IonicModule } from "@ionic/angular";

import { PlhActivityCheckInComponent } from "./activity-check-in.component";

describe("ActivityCheckInComponent", () => {
let component: PlhActivityCheckInComponent;
let fixture: ComponentFixture<PlhActivityCheckInComponent>;

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PlhActivityCheckInComponent],
imports: [IonicModule.forRoot()],
}).compileComponents();

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

it("should create", () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Component, OnInit } from "@angular/core";
import { TemplateBaseComponent } from "src/app/shared/components/template/components/base";
import { TemplateTranslateService } from "src/app/shared/components/template/services/template-translate.service";
import { getNumberParamFromTemplateRow, getStringParamFromTemplateRow } from "src/app/shared/utils";

interface IActivityCheckInParams {
/* TEMPLATE PARAMETER: "activity_id". The activity identifier attached at the bottom of the component */
id: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be renamed activity_id to improve clarity for authors

/* TEMPLATE PARAMETER: "title". The title attached at the bottom of the component */
title?: string;
/* TEMPLATE PARAMETER: "locked_icon_asset". The icon that shows when the activity is locked */
lockedIconAsset?: string;
/* TEMPLATE PARAMETER: "locked_image_asset". The illustration that shows when the activity is locked */
lockedImageAsset?: string;
/* TEMPLATE PARAMETER: "unlocked_icon_asset". The icon that shows when the activity is unlocked */
unlockedIconAsset?: string;
/* TEMPLATE PARAMETER: "unlocked_image_asset". The illustration that shows when the activity is locked */
unlockedImageAsset?: string;
/* TEMPLATE PARAMETER: "days_to_count_down". The illustration that shows when the activity is locked */
countDownDays?: number;
}

@Component({
selector: "plh-activity-check-in",
templateUrl: "./activity-check-in.component.html",
styleUrls: ["./activity-check-in.component.scss"],
})
export class PlhActivityCheckInComponent extends TemplateBaseComponent implements OnInit {
params: Partial<IActivityCheckInParams> = {};

daysLeft: number; // Progress of days left
progressPercentage: number = 16; // Initial progress

private unlockDate: Date;

constructor(public templateTranslateService: TemplateTranslateService) {
super();
}

ngOnInit() {
this.getParams();
this.daysLeft = this.params.countDownDays;
if (this._row.value) {
this.checkInTimer();
}
}

private getParams() {
this.params.id = getStringParamFromTemplateRow(this._row, "activity_id", null);
this.params.title = getStringParamFromTemplateRow(this._row, "title", null);
this.params.lockedIconAsset = getStringParamFromTemplateRow(
this._row,
"locked_icon_asset",
null
);
this.params.lockedImageAsset = getStringParamFromTemplateRow(
this._row,
"locked_image_asset",
null
);
this.params.unlockedIconAsset = getStringParamFromTemplateRow(
this._row,
"unlocked_icon_asset",
null
);
this.params.unlockedImageAsset = getStringParamFromTemplateRow(
this._row,
"unlocked_image_asset",
null
);
this.params.countDownDays = getNumberParamFromTemplateRow(this._row, "days_to_count_down", 6);
}

private getLocalStorageKey(): string {
return `activity_${this.params.id}`;
}

// Calculates the days until check in
private checkInTimer() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic will need to be reworked to handle how we actually plan on managing the unlock dates of the various activities. @esmeetewinkel, is that something we should discuss in our next meeting?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes let's discuss in our next meeting. My initial thoughts are in my comment below

const localStorageKey = this.getLocalStorageKey();
const storedDate = localStorage.getItem(localStorageKey);
if (storedDate) {
this.unlockDate = new Date(storedDate);
} else {
this.unlockDate = this.getMidnightOfDate(new Date());
this.unlockDate.setDate(this.unlockDate.getDate() + this.params.countDownDays);
localStorage.setItem(localStorageKey, this.unlockDate.toISOString());
}
const dailyInterval = this.getMillisecondsUntilMidnight(); // Count until midnight
setTimeout(() => {
this.updateProgress();
setInterval(() => this.updateProgress(), 24 * 60 * 60 * 1000);
}, dailyInterval);
}

// Update the progress bar and unlock state
private updateProgress(): void {
const now = this.getMidnightOfDate(new Date());
this.daysLeft = this.unlockDate.getTime() - now.getTime();

if (now < this.unlockDate) {
this.daysLeft = Math.ceil(
(this.unlockDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)
);
this.progressPercentage =
((this.params.countDownDays - this.daysLeft) / this.params.countDownDays) * 100;
} else {
this.progressPercentage = 16;
localStorage.removeItem(this.getLocalStorageKey());
}
}

// Get the number of milliseconds until midnight.
private getMillisecondsUntilMidnight(): number {
const now = new Date();
const midnight = this.getMidnightOfDate(now);
midnight.setDate(midnight.getDate() + 1);
return midnight.getTime() - now.getTime();
}

// Get the midnight of a given date.
private getMidnightOfDate(date: Date): Date {
const midnight = new Date(date);
midnight.setHours(0, 0, 0, 0);
return midnight;
}
}
78 changes: 78 additions & 0 deletions src/theme/themes/plh_kids_kw/_overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,84 @@ body[data-theme="plh_kids_kw"] {
}
}

// activity-check-in
plh-activity-check-in {
.activity-container {
width: 45%;
.wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.progress-tracker {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.wrapper {
width: 46%;
}
.progress-bar-container {
background-color: var(--ion-color-gray-200);
height: 14px;
border-radius: var(--ion-border-radius-rounded);
margin-right: 8px;
overflow: hidden;
width: 100%;
}
.progress-bar {
border-radius: var(--ion-border-radius-rounded);
height: 100%;
background-color: #e0b160;
transition:
width 0.5s ease,
background-color 0.5s ease;
}
.countdown-text {
color: var(--ion-color-gray-600);
font-weight: var(--font-weight-medium);
}
}
.image {
img {
width: 140px;
}
}
.unlocked .image {
img {
width: 170px;
}
}
.details {
display: flex;
flex-direction: row;
align-items: center;
margin-top: -5px;
.title h4 {
color: var(--ion-color-gray-600);
font-weight: var(--font-weight-bold);
}
.icon {
display: flex;
align-items: center;
img {
width: 28px;
}
}
}
}
.activity-container[data-language-direction~="rtl"] {
.progress-tracker {
.progress-bar-container {
margin-right: 0;
margin-left: 6px;
}
}
}
}

// Radio Button Group
plh-radio-group {
.btn_triangle {
Expand Down
Loading