mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-03-12 01:32:12 +00:00
feat: apply new layout overhaul
This commit is contained in:
committed by
Rainer Killinger
parent
f16e5394cc
commit
7bbdba5c0b
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
Reference in New Issue
Block a user