feat: apply new layout overhaul

This commit is contained in:
Andy Bastian
2022-08-08 11:01:00 +00:00
committed by Rainer Killinger
parent f16e5394cc
commit 7bbdba5c0b
228 changed files with 28387 additions and 1092 deletions

View File

@@ -1,77 +1,146 @@
<ion-header>
<ion-toolbar color="primary">
<ion-toolbar color="primary" mode="ios">
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-menu-button></ion-menu-button>
<ion-back-button
[defaultHref]="'..'"
[text]="'back' | translate | titlecase"
></ion-back-button>
</ion-buttons>
<ion-title>{{ 'profile.title' | translate | titlecase }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<section>
<ion-grid>
<ion-row>
<ion-col>
<ion-card *ngIf="userInfo">
<ion-card-content>
<ion-grid>
<ion-row>
<ion-col size="3">
<ion-thumbnail>
<ion-icon name="person"></ion-icon>
</ion-thumbnail>
</ion-col>
<ion-col size="9">
<ion-row class="main-info">
<span
>{{ userInfo.givenName }}
{{ userInfo.familyName }}</span
>
</ion-row>
<ion-row class="additional-info">
<span>{{ userInfo.email }}</span>
<span
>{{
'profile.userInfo.studentId' | translate | titlecase
}}: {{ userInfo.studentId }}</span
>
</ion-row>
</ion-col>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>
<ion-row>
<ion-col class="login">
<a
*ngIf="!data.default.loggedIn; else loggedIn"
(click)="signIn('default')"
>{{ 'profile.buttons.default.log_in' | translate | titlecase }}</a
<ion-content>
<section class="user-card-wrapper">
<ion-card class="user-card">
<ion-card-header>
<ion-img src="assets/imgs/logo.png"></ion-img>
<span>
{{
userInfo?.role
? (userInfo?.role | uppercase)
: ('profile.role_guest' | translate | uppercase)
}}
</span>
</ion-card-header>
<ion-card-content>
<ion-img
class="goethe-img"
src="assets/imgs/profile-card-head.jpeg"
></ion-img>
<ion-grid>
<ion-row>
<ion-col size="3"></ion-col>
<ion-col
*ngIf="data.default.loggedIn; else logInPrompt"
size="9"
class="main-info"
>
<ion-text class="full-name">
{{ userInfo?.givenName }}
{{ userInfo?.familyName }}
</ion-text>
<div class="matriculation-number">
<ion-label>
{{ 'profile.userInfo.studentId' | translate | uppercase }}
</ion-label>
<ion-text>
{{ userInfo?.studentId }}
</ion-text>
</div>
<div class="user-name">
<ion-label>
{{ 'profile.userInfo.username' | translate | uppercase }}
</ion-label>
<ion-text>{{ userInfo?.name }}</ion-text>
</div>
<div class="email">
<ion-label>
{{ 'profile.userInfo.email' | translate | uppercase }}
</ion-label>
<ion-text>
{{ userInfo?.email }}
</ion-text>
</div>
</ion-col>
<ng-template #logInPrompt>
<ion-col size="9">
<ion-text class="log-in-prompt">
{{ 'profile.userInfo.logInPrompt' | translate }}
</ion-text>
</ion-col>
</ng-template>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</section>
<section class="login">
<ion-label class="section-headline">
{{ 'profile.titleLogins' | translate | uppercase }}
</ion-label>
<swiper [config]="sliderOptions" slidesPerView="auto" class="card-swiper">
<ng-template swiperSlide *ngFor="let loginItem of logins">
<a
*ngIf="!data[loginItem][loggedIn]"
class="swiper-card card"
(click)="signIn(loginItem)"
>
<ion-label>
{{
'profile.buttons.' + loginItem + '.log_in' | translate | titlecase
}}
</ion-label>
</a>
<a
*ngIf="data[loginItem][loggedIn]"
class="swiper-card card"
(click)="signIn(loginItem)"
>
{{
'profile.buttons.' + loginItem + '.log_out' | translate | titlecase
}}
</a>
</ng-template>
</swiper>
</section>
<section class="courses">
<ion-label class="section-headline">
{{ 'profile.titleCourses' | translate | uppercase }}
</ion-label>
<ion-card class="courses-card">
<ion-card-header (click)="toggleCourseCardState()">
<span>{{ 'profile.courses.today' | translate | uppercase }}</span>
<ion-icon [name]="courseCardState" fill="red"></ion-icon>
</ion-card-header>
<ion-card-content
class="course-card"
[class.show-card]="courseCardState === courseCardEnum.expanded"
>
<ng-container *ngIf="myCoursesToday.length === 0">
<div class="no-course">
{{ 'profile.courses.no_courses' | translate }}
</div>
</ng-container>
<ng-container *ngFor="let myCourse of myCoursesToday">
<div
class="clickable"
[routerLink]="['/data-detail', myCourse.course.event.uid]"
>
<ng-template #loggedIn
><a (click)="signOut('default')">{{
'profile.buttons.default.log_out' | translate | titlecase
}}</a></ng-template
>
</ion-col>
</ion-row>
<ion-row>
<ion-col class="login">
<a
*ngIf="!data.paia.loggedIn; else paiaLoggedIn"
(click)="signIn('paia')"
>{{ 'profile.buttons.paia.log_in' | translate | titlecase }}</a
>
<ng-template #paiaLoggedIn
><a (click)="signOut('paia')">{{
'profile.buttons.paia.log_out' | translate | titlecase
}}</a></ng-template
>
</ion-col>
</ion-row>
</ion-grid>
<div>{{ myCourse?.startTime }} - {{ myCourse?.endTime }}</div>
<div>{{ myCourse?.course.event?.originalCategory }}</div>
<div [class.last]="!myCourse?.course.inPlace?.name">
{{ myCourse.course?.event?.name }}
</div>
<div
*ngIf="myCourse.course?.inPlace?.name"
[class.last]="myCourse.course?.inPlace?.name"
>
{{ myCourse.course?.inPlace.name }}
</div>
</div>
</ng-container>
</ion-card-content>
</ion-card>
</section>
</ion-content>

View File

@@ -1,16 +1,145 @@
:host {
ion-col.login {
text-align: center;
a {
cursor: pointer;
ion-content {
--background: var(--ion-color-light);
}
section {
margin-bottom: calc(2 * var(--spacing-lg) - var(--spacing-md));
padding: var(--spacing-md);
&:last-of-type {
margin-bottom: 0;
}
}
.section-headline {
margin-bottom: var(--spacing-md);
}
.user-card-wrapper {
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 75%;
max-height: 17vh;
background-color: var(--ion-color-primary);
z-index: -1;
}
.user-card {
border-radius: var(--border-radius-default);
position: relative;
margin: 0;
box-shadow: var(--shadow-profile-card);
max-width: 400px;
ion-card-header {
--background: var(--ion-color-tertiary);
display: flex;
align-items: center;
padding-top: var(--spacing-sm);
padding-bottom: var(--spacing-sm);
ion-img {
display: block;
height: 36px;
margin-right: auto;
}
span {
color: var(--ion-color-light);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-bold);
line-height: 1;
padding-top: 3px;
}
}
ion-card-content {
min-height: 15vh;
.goethe-img {
position: absolute;
opacity: 0.13;
height: 100%;
}
.main-info {
display: grid;
grid-template-areas: 'fullName fullName'
'matriculationNumber userName'
'email email';
ion-label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-bold);
color: var(--ion-color-medium-shade);
display: block;
}
ion-text {
font-size: var(--font-size-md);
font-weight: var(--font-weight-bold);
color: var(--ion-color-text);
display: block;
}
.full-name {
grid-area: fullName;
display: block;
font-size: var(--font-size-lg);
font-weight: var(--font-weight-bold);
margin-bottom: var(--spacing-sm);
}
.matriculation-number {
grid-area: matriculationNumber;
margin-bottom: var(--spacing-sm);
}
.user-name {
grid-area: userName;
margin-bottom: var(--spacing-sm);
}
.email {
grid-area: email;
}
}
.log-in-prompt {
margin: auto 0;
color: var(--ion-color-text);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semi-bold);
}
}
}
}
.login {
.swiper.card-swiper {
text-align: left;
.swiper-card {
padding: var(--spacing-lg) var(--spacing-md);
font-size: var(--font-size-md);
font-weight: var(--font-weight-black);
justify-content: center;
height: 120px;
cursor: pointer;
}
}
}
ion-thumbnail {
background: var(--placeholder-gray);
--size: 64px;
height: 80%;
width: 80%;
align-items: center;
padding: 10px;
margin: 0;
border-radius: var(--border-radius-default);
ion-icon {
width: 100%;
height: 100%;
@@ -22,4 +151,70 @@
font-weight: bold;
margin-bottom: 2px;
}
.courses {
.courses-card {
margin: 0;
box-shadow: none;
border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
background-color: unset;
max-width: 800px;
ion-card-header {
background-color: var(--ion-color-light-contrast);
border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
display: flex;
justify-content: space-between;
align-items: center;
span {
color: var(--ion-color-light);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-bold);
}
ion-icon {
color: var(--ion-color-light);
height: 20px;
width: 20px;
&::shadow svg.icon path {
fill: var(--ion-color-light);
}
}
}
ion-card-content {
margin: 0;
padding: 0;
background-color: var(--ion-color-primary-contrast);
border-radius: var(--border-radius-default);
max-height: 0;
overflow: hidden;
transition: max-height 250ms ease-in-out, padding 250ms ease-in-out, margin 250ms ease-in-out;
&.show-card {
height: 100%;
display: block;
margin: var(--spacing-xxl);
padding:var(--spacing-md);
max-height: var(--max-height);
}
div {
font-size: var(--font-size-md);
font-weight: var(--font-weight-black);
color: var(--ion-color-light-contrast);
text-align: center;
&.no-course {
padding: var(--spacing-xxl) var(--spacing-lg);
}
&.last {
margin-bottom: var(--spacing-xl);
}
}
}
}
}
}

View File

@@ -23,6 +23,9 @@ import {TranslateModule} from '@ngx-translate/core';
import {ConfigProvider} from '../../config/config.provider';
import {sampleAuthConfiguration} from '../../../_helpers/data/sample-configuration';
import {StorageProvider} from '../../storage/storage.provider';
import {ScheduleProvider} from '../../calendar/schedule.provider';
import {DataProvider} from '../../data/data.provider';
import {StAppsWebHttpClient} from '../../data/stapps-web-http-client.provider';
describe('ProfilePage', () => {
let component: ProfilePageComponent;
@@ -45,6 +48,10 @@ describe('ProfilePage', () => {
return sampleAuthConfiguration;
});
const webHttpClientMethodSpy = jasmine.createSpyObj('StAppsWebHttpClient', [
'request',
]);
TestBed.configureTestingModule({
declarations: [ProfilePageComponent],
imports: [
@@ -56,6 +63,9 @@ describe('ProfilePage', () => {
providers: [
{provide: ConfigProvider, useValue: configProvider},
{provide: StorageProvider, useValue: storageProvider},
{provide: StAppsWebHttpClient, useValue: webHttpClientMethodSpy},
ScheduleProvider,
DataProvider,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();

View File

@@ -13,38 +13,75 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, OnInit} from '@angular/core';
import {IonicUserInfoHandler} from 'ionic-appauth';
import {Requestor, TokenResponse} from '@openid/appauth';
import {Subscription} from 'rxjs';
import {AuthHelperService} from '../../auth/auth-helper.service';
import {
SCAuthorizationProviderType,
SCDateSeries,
SCUserConfiguration,
} from '@openstapps/core';
import {ActivatedRoute} from '@angular/router';
import {ScheduleProvider} from '../../calendar/schedule.provider';
import moment from 'moment';
enum CourseCard {
collapsed = 'caret-up-sharp',
expanded = 'caret-down-sharp',
}
interface MyCoursesTodayInterface {
startTime: string;
endTime: string;
course: SCDateSeries;
}
@Component({
selector: 'app-home',
templateUrl: './profile-page.component.html',
styleUrls: ['./profile-page.component.scss'],
})
export class ProfilePageComponent implements OnInit, OnDestroy {
export class ProfilePageComponent implements OnInit {
data: {[key in SCAuthorizationProviderType]: {loggedIn: boolean}} = {
default: {loggedIn: false},
paia: {loggedIn: false},
};
logins: SCAuthorizationProviderType[] = [];
originPath: string | null;
userInfo?: SCUserConfiguration;
/**
* Slider options
*/
sliderOptions = {
spaceBetween: 12,
freeMode: {
enabled: true,
sticky: true,
},
width: 130,
};
courseCardEnum = CourseCard;
courseCardState = CourseCard.expanded;
todayDate = moment().startOf('day').add(0, 'day').format(); // moment().startOf('day').format(); '2022-05-03T00:00:00+02:00'
myCoursesToday: MyCoursesTodayInterface[] = [];
subscriptions: Subscription[] = [];
constructor(
private requestor: Requestor,
private authHelper: AuthHelperService,
private route: ActivatedRoute,
protected readonly scheduleProvider: ScheduleProvider,
) {}
ngOnInit() {
@@ -76,6 +113,19 @@ export class ProfilePageComponent implements OnInit, OnDestroy {
this.originPath = queryParameters.get('origin_path');
}),
);
this.getMyCourses();
for (const dataKey in this.data) {
switch (dataKey) {
case 'default':
this.logins.push('default');
break;
case 'paia':
this.logins.push('paia');
break;
}
}
}
getUserInfo(token: TokenResponse) {
@@ -91,6 +141,27 @@ export class ProfilePageComponent implements OnInit, OnDestroy {
});
}
async getMyCourses() {
const uuidSubscription = this.scheduleProvider.uuids$.subscribe(
async result => {
const courses = await this.scheduleProvider.getDateSeries(result);
for (const course of courses.dates) {
for (const date of course.dates) {
if (moment(date).startOf('day').format() === this.todayDate) {
this.myCoursesToday[this.myCoursesToday.length] = {
startTime: moment(date).format('LT'),
endTime: moment(date).add(course.duration).format('LT'),
course,
};
}
}
}
uuidSubscription.unsubscribe();
},
);
}
async signIn(providerType: SCAuthorizationProviderType) {
await this.handleOriginPath();
this.authHelper.getProvider(providerType).signIn();
@@ -101,10 +172,19 @@ export class ProfilePageComponent implements OnInit, OnDestroy {
this.userInfo = undefined;
}
ngOnDestroy(): void {
for (const subscription of this.subscriptions) {
subscription.unsubscribe();
toggleCourseCardState() {
if (this.courseCardState === CourseCard.expanded) {
const card: HTMLElement | null = document.querySelector('.course-card');
const height = card?.scrollHeight;
if (card && height) {
card.style.setProperty('--max-height', height + 'px');
}
}
this.courseCardState =
this.courseCardState === CourseCard.expanded
? CourseCard.collapsed
: CourseCard.expanded;
}
private async handleOriginPath() {

View File

@@ -20,6 +20,8 @@ import {Routes, RouterModule} from '@angular/router';
import {IonicModule} from '@ionic/angular';
import {ProfilePageComponent} from './page/profile-page.component';
import {TranslateModule} from '@ngx-translate/core';
import {SwiperModule} from 'swiper/angular';
import {UtilModule} from '../../util/util.module';
const routes: Routes = [
{
@@ -36,6 +38,8 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
TranslateModule,
SwiperModule,
UtilModule,
],
})
export class ProfilePageModule {}