Add student picker window
This commit is contained in:
parent
d7ee500ba6
commit
c9fee0cad4
6 changed files with 206 additions and 20 deletions
|
@ -45,6 +45,12 @@ export default async function registerWebsiteApi(server: MyFastifyInstance): Pro
|
|||
server.log.error(error);
|
||||
throw server.httpErrors.internalServerError();
|
||||
}
|
||||
// TODO: Find why the promise never resolves
|
||||
reply.clearCookie(`epk-${request.query.prompt_id}`);
|
||||
// In case execution of setCookie takes some time
|
||||
// TODO: Remove
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const sessionData = getSessionData(request.session);
|
||||
const prompt = sessionData.prompts.get(request.query.prompt_id);
|
||||
if (!prompt) throw server.httpErrors.badRequest('Prompt data not found');
|
||||
|
|
|
@ -56,7 +56,7 @@ export default class LoginResolver {
|
|||
availableStudentIds: students.map(({ studentId }) => studentId),
|
||||
};
|
||||
// TODO: Find why the promise never resolves
|
||||
reply.setCookie('epk', encryptedPrivateKey, {
|
||||
reply.setCookie(`epk-${promptId}`, encryptedPrivateKey, {
|
||||
sameSite: 'strict',
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-form @submit.prevent="submit">
|
||||
<v-form @submit.prevent="submit" v-model="formValid" ref="form">
|
||||
<v-card-title class="d-block">Zaloguj się do konta VULCAN UONET+</v-card-title>
|
||||
<div class="mx-4">
|
||||
<v-select v-model="host" label="Odmiana dziennika" :items="hosts" outlined />
|
||||
<v-text-field v-model="username" label="Nazwa użytkownika" outlined />
|
||||
<v-text-field v-model="password" type="password" label="Hasło" outlined />
|
||||
<v-text-field
|
||||
v-model="username"
|
||||
label="Nazwa użytkownika"
|
||||
outlined
|
||||
:rules="requiredRules"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
type="password"
|
||||
label="Hasło"
|
||||
outlined
|
||||
:rules="requiredRules"
|
||||
/>
|
||||
</div>
|
||||
<v-alert type="error" class="mx-2" :value="error === 'invalid-credentials'">
|
||||
Dane logowania są nieprawidłowe
|
||||
|
@ -18,7 +29,7 @@
|
|||
Wróć
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn color="primary" :loading="loading" type="submit">
|
||||
<v-btn color="primary" :loading="loading" type="submit" :disabled="!formValid">
|
||||
Zaloguj się
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
|
@ -27,9 +38,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import {
|
||||
Component, Prop, Ref, Vue,
|
||||
} from 'vue-property-decorator';
|
||||
import { PromptInfo } from '@/types';
|
||||
import { hasErrorCode, sdk } from '@/pages/authenticate-prompt/sdk';
|
||||
import { InputValidationRules } from 'vuetify';
|
||||
|
||||
interface VForm extends HTMLFormElement {
|
||||
validate(): boolean;
|
||||
resetValidation(): void;
|
||||
}
|
||||
|
||||
@Component({
|
||||
name: 'LoginWindow',
|
||||
|
@ -41,6 +60,8 @@ export default class LoginWindow extends Vue {
|
|||
})
|
||||
promptInfo!: PromptInfo;
|
||||
|
||||
@Ref('form') form!: VForm;
|
||||
|
||||
readonly hosts = [
|
||||
{
|
||||
text: 'Vulcan',
|
||||
|
@ -52,6 +73,12 @@ export default class LoginWindow extends Vue {
|
|||
},
|
||||
]
|
||||
|
||||
readonly requiredRules: InputValidationRules = [
|
||||
(v) => v !== '' || 'To pole jest wymagane',
|
||||
]
|
||||
|
||||
formValid = false;
|
||||
|
||||
host = '';
|
||||
|
||||
username = '';
|
||||
|
@ -66,10 +93,11 @@ export default class LoginWindow extends Vue {
|
|||
this.host = 'fakelog.cf';
|
||||
this.username = '';
|
||||
this.password = '';
|
||||
this.form.resetValidation();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.loading) return;
|
||||
if (this.loading || !this.formValid) return;
|
||||
this.error = null;
|
||||
this.loading = true;
|
||||
try {
|
||||
|
@ -80,7 +108,7 @@ export default class LoginWindow extends Vue {
|
|||
password: this.password,
|
||||
});
|
||||
const { students } = login;
|
||||
console.log(students);
|
||||
this.$emit('login', { students });
|
||||
this.reset();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -94,7 +122,7 @@ export default class LoginWindow extends Vue {
|
|||
this.$emit('back');
|
||||
}
|
||||
|
||||
created() {
|
||||
mounted() {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-form @submit.prevent="submit">
|
||||
<v-card-title
|
||||
v-if="mode === StudentsMode.One"
|
||||
>
|
||||
Wybierz ucznia
|
||||
</v-card-title>
|
||||
<v-card-title
|
||||
v-else
|
||||
>
|
||||
Wybierz uczniów
|
||||
</v-card-title>
|
||||
<v-alert
|
||||
v-if="mode === StudentsMode.None"
|
||||
type="info"
|
||||
text
|
||||
class="mx-2"
|
||||
>
|
||||
Aplikacja nie wymaga dostępu do dzienników uczniów
|
||||
</v-alert>
|
||||
<template v-else>
|
||||
<v-divider />
|
||||
<v-list>
|
||||
<v-list-item-group
|
||||
v-model="studentsValue"
|
||||
:multiple="mode === StudentsMode.Many"
|
||||
color="primary"
|
||||
>
|
||||
<v-list-item
|
||||
v-for="student in students"
|
||||
:key="student.studentId"
|
||||
:value="student.studentId"
|
||||
>
|
||||
<template v-slot:default="{ active }">
|
||||
<v-list-item-action>
|
||||
<v-icon v-if="active">
|
||||
{{ mode === StudentsMode.Many ? '$checkboxOn' : '$radioOn' }}
|
||||
</v-icon>
|
||||
<v-icon v-else>
|
||||
{{ mode === StudentsMode.Many ? '$checkboxOff' : '$radioOff' }}
|
||||
</v-icon>
|
||||
</v-list-item-action>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ student.name }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
<v-divider />
|
||||
</template>
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" text outlined @click="back">
|
||||
Wróć
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn color="primary" :href="allowUrl" type="submit" :disabled="!valid">
|
||||
Przydziel dostęp
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { PromptInfo, Student, StudentsMode } from '@/types';
|
||||
|
||||
@Component({
|
||||
name: 'StudentsWindow',
|
||||
})
|
||||
export default class StudentsWindow extends Vue {
|
||||
@Prop({
|
||||
required: true,
|
||||
type: Object,
|
||||
})
|
||||
promptInfo!: PromptInfo
|
||||
|
||||
@Prop({
|
||||
required: true,
|
||||
type: Array,
|
||||
})
|
||||
students!: Student[]
|
||||
|
||||
readonly StudentsMode = StudentsMode;
|
||||
|
||||
get mode() {
|
||||
return this.promptInfo.studentsMode;
|
||||
}
|
||||
|
||||
get studentsValue() {
|
||||
if (this.mode === StudentsMode.One) {
|
||||
return this.pickedStudents[0] ?? undefined;
|
||||
}
|
||||
return this.pickedStudents;
|
||||
}
|
||||
|
||||
set studentsValue(value: string[] | string | undefined) {
|
||||
if (value === undefined) this.pickedStudents = [];
|
||||
else if (value instanceof Array) this.pickedStudents = value;
|
||||
else this.pickedStudents = [value];
|
||||
}
|
||||
|
||||
pickedStudents: string[] = [];
|
||||
|
||||
get valid() {
|
||||
if (this.mode === StudentsMode.None) return this.pickedStudents.length === 0;
|
||||
if (this.mode === StudentsMode.One) return this.pickedStudents.length === 1;
|
||||
if (this.mode === StudentsMode.Many) return this.pickedStudents.length >= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
get allowUrl() {
|
||||
if (!this.valid) return null;
|
||||
if (this.mode === StudentsMode.One) return `/api/website/allow?prompt_id=${this.promptInfo.id}`;
|
||||
const studentIds = this.pickedStudents.map(encodeURIComponent).join('+');
|
||||
return `/api/website/allow?prompt_id=${this.promptInfo.id}&student_ids=${studentIds}`;
|
||||
}
|
||||
|
||||
back() {
|
||||
this.$emit('back');
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
<template v-else>
|
||||
<div class="pb-1 text--secondary">
|
||||
Krok <span class="primary--text">{{ step }}/5</span>
|
||||
Krok <span class="primary--text">{{ step }}/3</span>
|
||||
</div>
|
||||
<v-card outlined>
|
||||
<div class="d-flex justify-center mn-16 avatar__wrapper">
|
||||
|
@ -70,12 +70,25 @@
|
|||
<v-window :value="step">
|
||||
<v-window-item :value="1">
|
||||
<overview-window
|
||||
@next="overviewNext"
|
||||
:promptInfo="promptInfo"
|
||||
@next="toLoginWindow"
|
||||
/>
|
||||
</v-window-item>
|
||||
<v-window-item :value="2" eager>
|
||||
<login-window @back="loginBack" ref="loginWindow" :prompt-info="promptInfo" />
|
||||
<login-window
|
||||
ref="loginWindow"
|
||||
:prompt-info="promptInfo"
|
||||
@login="login"
|
||||
@back="loginBack"
|
||||
/>
|
||||
</v-window-item>
|
||||
<v-window-item :value="3">
|
||||
<students-window
|
||||
v-if="students !== null"
|
||||
:prompt-info="promptInfo"
|
||||
:students="students"
|
||||
@back="toLoginWindow"
|
||||
/>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-card>
|
||||
|
@ -115,13 +128,14 @@
|
|||
<script lang="ts">
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator';
|
||||
import OverviewWindow from '@/compontents/authenticate-prompt-windows/overview-window.vue';
|
||||
import { PromptInfo } from '@/types';
|
||||
import { PromptInfo, Student } from '@/types';
|
||||
import LoginWindow from '@/compontents/authenticate-prompt-windows/login-window.vue';
|
||||
import StudentsWindow from '@/compontents/authenticate-prompt-windows/students-window.vue';
|
||||
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
||||
|
||||
@Component({
|
||||
name: 'AuthenticatePromptApp',
|
||||
components: { LoginWindow, OverviewWindow },
|
||||
components: { LoginWindow, OverviewWindow, StudentsWindow },
|
||||
})
|
||||
export default class AuthenticatePromptApp extends Vue {
|
||||
@Ref() readonly loginWindow!: LoginWindow
|
||||
|
@ -132,6 +146,8 @@ export default class AuthenticatePromptApp extends Vue {
|
|||
|
||||
promptInfoError = false;
|
||||
|
||||
students: Student[] | null = null;
|
||||
|
||||
step = 1;
|
||||
|
||||
async loadPromptInfo() {
|
||||
|
@ -158,13 +174,19 @@ export default class AuthenticatePromptApp extends Vue {
|
|||
await this.loadPromptInfo();
|
||||
}
|
||||
|
||||
toLoginWindow() {
|
||||
this.step = 2;
|
||||
this.loginWindow.reset();
|
||||
this.students = null;
|
||||
}
|
||||
|
||||
loginBack() {
|
||||
this.step = 1;
|
||||
}
|
||||
|
||||
overviewNext() {
|
||||
this.step = 2;
|
||||
this.loginWindow.reset();
|
||||
login({ students }: { students: Student[] }) {
|
||||
this.students = students;
|
||||
this.step = 3;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export enum StudentsMode {
|
||||
None = 'none',
|
||||
One = 'one',
|
||||
Many = 'many',
|
||||
None = 'None',
|
||||
One = 'One',
|
||||
Many = 'Many',
|
||||
}
|
||||
|
||||
export interface PromptInfo {
|
||||
|
@ -15,3 +15,8 @@ export interface PromptInfo {
|
|||
verified: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Student {
|
||||
studentId: string;
|
||||
name: string;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue