Add login window (not finished)
This commit is contained in:
parent
be486b6da6
commit
c1c6812180
11 changed files with 325 additions and 129 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
import { ApolloError } from 'apollo-server-fastify';
|
||||||
|
|
||||||
export class UnknownPromptError extends Error {
|
export class UnknownPromptError extends Error {
|
||||||
public name = 'UnknownPromptError';
|
public name = 'UnknownPromptError';
|
||||||
|
|
||||||
|
@ -6,10 +8,10 @@ export class UnknownPromptError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InvalidCredentialsError extends Error {
|
export class InvalidVulcanCredentialsError extends ApolloError {
|
||||||
public name = 'InvalidCredentialsError';
|
public name = 'InvalidVulcanCredentialsError';
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super('User with provided credentials not found');
|
super('Invalid vulcan credentials', 'INVALID_VULCAN_CREDENTIALS');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ import {
|
||||||
Arg, Ctx, Mutation, Resolver,
|
Arg, Ctx, Mutation, Resolver,
|
||||||
} from 'type-graphql';
|
} from 'type-graphql';
|
||||||
import {
|
import {
|
||||||
encryptSymmetrical, encryptWithPublicKey, generatePrivatePublicPair, requireEnvHex,
|
encryptSymmetrical, encryptWithPublicKey, generatePrivatePublicPair, isObject, requireEnvHex,
|
||||||
} from '../../../utils';
|
} from '../../../utils';
|
||||||
import { UnknownPromptError } from '../errors';
|
import { InvalidVulcanCredentialsError, UnknownPromptError } from '../errors';
|
||||||
import LoginResult from '../models/login-result';
|
import LoginResult from '../models/login-result';
|
||||||
import type LoginResultStudent from '../models/login-result-student';
|
import type LoginResultStudent from '../models/login-result-student';
|
||||||
import type { WebsiteAPIContext } from '../types';
|
import type { WebsiteAPIContext } from '../types';
|
||||||
|
@ -29,7 +29,12 @@ export default class LoginResolver {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
}));
|
}));
|
||||||
|
try {
|
||||||
await client.login();
|
await client.login();
|
||||||
|
} catch (error) {
|
||||||
|
if (isObject(error) && error.name === 'InvalidCredentialsError') throw new InvalidVulcanCredentialsError();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
const diaryList = await client.getDiaryList();
|
const diaryList = await client.getDiaryList();
|
||||||
const { privateKey, publicKey } = await generatePrivatePublicPair();
|
const { privateKey, publicKey } = await generatePrivatePublicPair();
|
||||||
const encryptedPrivateKey = encryptSymmetrical(
|
const encryptedPrivateKey = encryptSymmetrical(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
schema: http://localhost:3000/api/website/graphql
|
schema: http://localhost:3000/api/website/graphql
|
||||||
documents:
|
documents:
|
||||||
- src/graphql/queries/get-prompt-info.ts
|
- src/graphql/queries/get-prompt-info.ts
|
||||||
|
- src/graphql/mutations/login.ts
|
||||||
generates:
|
generates:
|
||||||
./src/graphql/generated.ts:
|
./src/graphql/generated.ts:
|
||||||
plugins:
|
plugins:
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-form @submit.prevent="submit">
|
||||||
|
<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 />
|
||||||
|
</div>
|
||||||
|
<v-alert type="error" class="mx-2" :value="error === 'invalid-credentials'">
|
||||||
|
Dane logowania są nieprawidłowe
|
||||||
|
</v-alert>
|
||||||
|
<v-alert type="error" class="mx-2" :value="error === 'other'">
|
||||||
|
Podczas logowania wystąpił błąd
|
||||||
|
</v-alert>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="primary" text outlined @click="back" :disabled="loading">
|
||||||
|
Wróć
|
||||||
|
</v-btn>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn color="primary" :loading="loading" type="submit">
|
||||||
|
Zaloguj się
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import { PromptInfo } from '@/types';
|
||||||
|
import { hasErrorCode, sdk } from '@/pages/authenticate-prompt/sdk';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'LoginWindow',
|
||||||
|
})
|
||||||
|
export default class LoginWindow extends Vue {
|
||||||
|
@Prop({
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
|
})
|
||||||
|
promptInfo!: PromptInfo;
|
||||||
|
|
||||||
|
readonly hosts = [
|
||||||
|
{
|
||||||
|
text: 'Vulcan',
|
||||||
|
value: 'vulcan.net.pl',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Fakelog',
|
||||||
|
value: 'fakelog.cf',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
host = '';
|
||||||
|
|
||||||
|
username = '';
|
||||||
|
|
||||||
|
password = '';
|
||||||
|
|
||||||
|
loading = false;
|
||||||
|
|
||||||
|
error: 'invalid-credentials' | 'other' | null = null;
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.host = 'fakelog.cf';
|
||||||
|
this.username = '';
|
||||||
|
this.password = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (this.loading) return;
|
||||||
|
this.error = null;
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const { login } = await sdk.Login({
|
||||||
|
promptId: this.promptInfo.id,
|
||||||
|
host: this.host,
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
});
|
||||||
|
const { students, encryptedPrivateKey } = login;
|
||||||
|
console.log(students, encryptedPrivateKey);
|
||||||
|
this.reset();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this.error = hasErrorCode(error, 'INVALID_VULCAN_CREDENTIALS') ? 'invalid-credentials' : 'other';
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
back() {
|
||||||
|
if (this.loading) return;
|
||||||
|
this.$emit('back');
|
||||||
|
}
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,102 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="pt-16">
|
||||||
|
<h2 class="text-subtitle-1 text--secondary mt-6 mb-2 px-4">
|
||||||
|
Aplikacja
|
||||||
|
<span class="text--primary">{{ promptInfo.application.name }}</span>
|
||||||
|
chce uzyskać dostęp do twojego konta VULCAN UONET+ przez Wulkanowy Bridge
|
||||||
|
</h2>
|
||||||
|
<v-subheader>Uprawnienia aplikacji</v-subheader>
|
||||||
|
<v-list subheader>
|
||||||
|
<v-list-item v-for="item in scopeItems" :key="item.key">
|
||||||
|
<v-list-item-icon>
|
||||||
|
<v-icon>{{ item.icon }}</v-icon>
|
||||||
|
</v-list-item-icon>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
{{ item.title }}
|
||||||
|
</v-list-item-title>
|
||||||
|
<v-list-item-subtitle v-if="item.subtitle !== undefined">
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
<v-alert color="info" text class="mb-2 mx-2">
|
||||||
|
<span class="font-weight-medium">{{ promptInfo.application.name }}</span>
|
||||||
|
nie zobaczy twojego hasła
|
||||||
|
<template #append>
|
||||||
|
<!-- TODO: Implement -->
|
||||||
|
<v-btn text color="info">Więcej</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="primary" text outlined :href="denyUrl">
|
||||||
|
Odmów
|
||||||
|
</v-btn>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn color="primary" @click="next">
|
||||||
|
Dalej
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import { PromptInfo } from '@/types';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'OverviewWindow',
|
||||||
|
})
|
||||||
|
export default class OverviewWindow extends Vue {
|
||||||
|
@Prop({
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
|
})
|
||||||
|
promptInfo!: PromptInfo;
|
||||||
|
|
||||||
|
readonly scopeDescriptions: {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
icon: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: 'timetable',
|
||||||
|
title: 'Plan lekcji',
|
||||||
|
icon: 'mdi-timetable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'grades',
|
||||||
|
title: 'Oceny i punkty',
|
||||||
|
subtitle: 'Oceny cząstkowe, końcowe, opisowe oraz punkty',
|
||||||
|
icon: 'mdi-numeric-6-box-multiple-outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'notes',
|
||||||
|
title: 'Uwagi i pochwały',
|
||||||
|
icon: 'mdi-note-text-outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'achievements',
|
||||||
|
title: 'Osiągnięcia',
|
||||||
|
icon: 'mdi-trophy-outline',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
get scopeItems() {
|
||||||
|
return this.scopeDescriptions
|
||||||
|
.filter(({ key }) => this.promptInfo.scopes.includes(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
get denyUrl() {
|
||||||
|
return `/api/website/deny?prompt_id=${this.promptInfo.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
this.$emit('next');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -71,6 +71,25 @@ export type LoginResultStudent = {
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LoginMutationVariables = Exact<{
|
||||||
|
promptId: Scalars['String'];
|
||||||
|
host: Scalars['String'];
|
||||||
|
username: Scalars['String'];
|
||||||
|
password: Scalars['String'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type LoginMutation = (
|
||||||
|
{ __typename?: 'Mutation' }
|
||||||
|
& { login: (
|
||||||
|
{ __typename?: 'LoginResult' }
|
||||||
|
& Pick<LoginResult, 'encryptedPrivateKey'>
|
||||||
|
& { students: Array<(
|
||||||
|
{ __typename?: 'LoginResultStudent' }
|
||||||
|
& Pick<LoginResultStudent, 'studentId' | 'name'>
|
||||||
|
)>; }
|
||||||
|
); }
|
||||||
|
);
|
||||||
|
|
||||||
export type GetPromptInfoQueryVariables = Exact<{
|
export type GetPromptInfoQueryVariables = Exact<{
|
||||||
promptId: Scalars['String'];
|
promptId: Scalars['String'];
|
||||||
}>;
|
}>;
|
||||||
|
@ -79,7 +98,7 @@ export type GetPromptInfoQuery = (
|
||||||
{ __typename?: 'Query' }
|
{ __typename?: 'Query' }
|
||||||
& { promptInfo: (
|
& { promptInfo: (
|
||||||
{ __typename?: 'PromptInfo' }
|
{ __typename?: 'PromptInfo' }
|
||||||
& Pick<PromptInfo, 'scopes' | 'studentsMode'>
|
& Pick<PromptInfo, 'id' | 'scopes' | 'studentsMode'>
|
||||||
& { application: (
|
& { application: (
|
||||||
{ __typename?: 'PromptInfoApplication' }
|
{ __typename?: 'PromptInfoApplication' }
|
||||||
& Pick<PromptInfoApplication, 'name' | 'iconUrl' | 'iconColor' | 'verified'>
|
& Pick<PromptInfoApplication, 'name' | 'iconUrl' | 'iconColor' | 'verified'>
|
||||||
|
@ -87,9 +106,26 @@ export type GetPromptInfoQuery = (
|
||||||
); }
|
); }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const LoginDocument = gql`
|
||||||
|
mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!) {
|
||||||
|
login(
|
||||||
|
host: $host
|
||||||
|
password: $password
|
||||||
|
username: $username
|
||||||
|
promptId: $promptId
|
||||||
|
) {
|
||||||
|
encryptedPrivateKey
|
||||||
|
students {
|
||||||
|
studentId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const GetPromptInfoDocument = gql`
|
export const GetPromptInfoDocument = gql`
|
||||||
query GetPromptInfo($promptId: String!) {
|
query GetPromptInfo($promptId: String!) {
|
||||||
promptInfo(promptId: $promptId) {
|
promptInfo(promptId: $promptId) {
|
||||||
|
id
|
||||||
scopes
|
scopes
|
||||||
studentsMode
|
studentsMode
|
||||||
application {
|
application {
|
||||||
|
@ -107,6 +143,9 @@ export type SdkFunctionWrapper = <T>(action: () => Promise<T>) => Promise<T>;
|
||||||
const defaultWrapper: SdkFunctionWrapper = (sdkFunction) => sdkFunction();
|
const defaultWrapper: SdkFunctionWrapper = (sdkFunction) => sdkFunction();
|
||||||
export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) {
|
export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) {
|
||||||
return {
|
return {
|
||||||
|
Login(variables: LoginMutationVariables, requestHeaders?: Headers): Promise<LoginMutation> {
|
||||||
|
return withWrapper(() => client.request<LoginMutation>(print(LoginDocument), variables, requestHeaders));
|
||||||
|
},
|
||||||
GetPromptInfo(variables: GetPromptInfoQueryVariables, requestHeaders?: Headers): Promise<GetPromptInfoQuery> {
|
GetPromptInfo(variables: GetPromptInfoQueryVariables, requestHeaders?: Headers): Promise<GetPromptInfoQuery> {
|
||||||
return withWrapper(() => client.request<GetPromptInfoQuery>(print(GetPromptInfoDocument), variables, requestHeaders));
|
return withWrapper(() => client.request<GetPromptInfoQuery>(print(GetPromptInfoDocument), variables, requestHeaders));
|
||||||
},
|
},
|
||||||
|
|
12
website/src/graphql/mutations/login.ts
Normal file
12
website/src/graphql/mutations/login.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export default gql`mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!) {
|
||||||
|
login(host: $host, password: $password, username: $username, promptId: $promptId) {
|
||||||
|
encryptedPrivateKey
|
||||||
|
students {
|
||||||
|
studentId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
|
@ -2,6 +2,7 @@ import gql from 'graphql-tag';
|
||||||
|
|
||||||
export default gql`query GetPromptInfo($promptId: String!) {
|
export default gql`query GetPromptInfo($promptId: String!) {
|
||||||
promptInfo(promptId: $promptId) {
|
promptInfo(promptId: $promptId) {
|
||||||
|
id
|
||||||
scopes
|
scopes
|
||||||
studentsMode
|
studentsMode
|
||||||
application {
|
application {
|
||||||
|
@ -11,4 +12,5 @@ export default gql`query GetPromptInfo($promptId: String!) {
|
||||||
verified
|
verified
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`;
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -69,62 +69,13 @@
|
||||||
</div>
|
</div>
|
||||||
<v-window :value="step">
|
<v-window :value="step">
|
||||||
<v-window-item :value="1">
|
<v-window-item :value="1">
|
||||||
<div>
|
<overview-window
|
||||||
<div class="pt-16">
|
@next="overviewNext"
|
||||||
<h2 class="text-subtitle-1 text--secondary mt-6 mb-2 px-4">
|
:promptInfo="promptInfo"
|
||||||
Aplikacja
|
/>
|
||||||
<span class="text--primary">{{ promptInfo.application.name }}</span>
|
|
||||||
chce uzyskać dostęp do twojego konta VULCAN UONET+ przez Wulkanowy Bridge
|
|
||||||
</h2>
|
|
||||||
<v-subheader>Uprawnienia aplikacji</v-subheader>
|
|
||||||
<v-list subheader>
|
|
||||||
<v-list-item v-for="item in scopeItems" :key="item.key">
|
|
||||||
<v-list-item-icon>
|
|
||||||
<v-icon>{{ item.icon }}</v-icon>
|
|
||||||
</v-list-item-icon>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>
|
|
||||||
{{ item.title }}
|
|
||||||
</v-list-item-title>
|
|
||||||
<v-list-item-subtitle v-if="item.subtitle !== undefined">
|
|
||||||
{{ item.subtitle }}
|
|
||||||
</v-list-item-subtitle>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
<v-alert color="info" text class="mb-2 mx-2">
|
|
||||||
<span class="font-weight-medium">{{ promptInfo.application.name }}</span>
|
|
||||||
nie zobaczy twojego hasła
|
|
||||||
<template #append>
|
|
||||||
<!-- TODO: Implement -->
|
|
||||||
<v-btn text color="info">Więcej</v-btn>
|
|
||||||
</template>
|
|
||||||
</v-alert>
|
|
||||||
<v-divider />
|
|
||||||
</div>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-btn color="primary" text outlined :href="denyUrl">
|
|
||||||
Odmów
|
|
||||||
</v-btn>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn color="primary" @click="beginLogin">
|
|
||||||
Dalej
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</div>
|
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
<v-window-item :value="2">
|
<v-window-item :value="2" eager>
|
||||||
<div>
|
<login-window @back="loginBack" ref="loginWindow" :prompt-info="promptInfo" />
|
||||||
<v-card-actions>
|
|
||||||
<v-btn color="primary" text outlined @click="goBack">
|
|
||||||
Wróć
|
|
||||||
</v-btn>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn color="primary">
|
|
||||||
Zaloguj się
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</div>
|
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
</v-window>
|
</v-window>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
@ -162,31 +113,19 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Ref, Vue } from 'vue-property-decorator';
|
||||||
import { GraphQLClient } from 'graphql-request';
|
import OverviewWindow from '@/compontents/authenticate-prompt-windows/overview-window.vue';
|
||||||
import { getSdk } from '@/graphql/generated';
|
import { PromptInfo } from '@/types';
|
||||||
|
import LoginWindow from '@/compontents/authenticate-prompt-windows/login-window.vue';
|
||||||
export enum StudentsMode {
|
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
||||||
None = 'none',
|
|
||||||
One = 'one',
|
|
||||||
Many = 'many',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PromptInfo {
|
|
||||||
scopes: string[];
|
|
||||||
studentsMode: StudentsMode;
|
|
||||||
application: {
|
|
||||||
name: string;
|
|
||||||
iconUrl: string | null;
|
|
||||||
iconColor: string;
|
|
||||||
verified: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'AuthenticatePromptApp',
|
name: 'AuthenticatePromptApp',
|
||||||
|
components: { LoginWindow, OverviewWindow },
|
||||||
})
|
})
|
||||||
export default class AuthenticatePromptApp extends Vue {
|
export default class AuthenticatePromptApp extends Vue {
|
||||||
|
@Ref() readonly loginWindow!: LoginWindow
|
||||||
|
|
||||||
promptInfo: PromptInfo | null = null;
|
promptInfo: PromptInfo | null = null;
|
||||||
|
|
||||||
promptId: string | null = null;
|
promptId: string | null = null;
|
||||||
|
@ -195,55 +134,12 @@ export default class AuthenticatePromptApp extends Vue {
|
||||||
|
|
||||||
step = 1;
|
step = 1;
|
||||||
|
|
||||||
readonly scopeDescriptions: {
|
|
||||||
key: string;
|
|
||||||
title: string;
|
|
||||||
subtitle?: string;
|
|
||||||
icon: string;
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
key: 'timetable',
|
|
||||||
title: 'Plan lekcji',
|
|
||||||
icon: 'mdi-timetable',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'grades',
|
|
||||||
title: 'Oceny i punkty',
|
|
||||||
subtitle: 'Oceny cząstkowe, końcowe, opisowe oraz punkty',
|
|
||||||
icon: 'mdi-numeric-6-box-multiple-outline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'notes',
|
|
||||||
title: 'Uwagi i pochwały',
|
|
||||||
icon: 'mdi-note-text-outline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'achievements',
|
|
||||||
title: 'Osiągnięcia',
|
|
||||||
icon: 'mdi-trophy-outline',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
get scopeItems() {
|
|
||||||
if (this.promptInfo === null) return undefined;
|
|
||||||
return this.scopeDescriptions
|
|
||||||
.filter(({ key }) => this.promptInfo?.scopes?.includes(key) ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
get denyUrl() {
|
|
||||||
if (!this.promptId) return undefined;
|
|
||||||
return `/api/website/deny?prompt_id=${this.promptId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadPromptInfo() {
|
async loadPromptInfo() {
|
||||||
this.promptInfoError = false;
|
this.promptInfoError = false;
|
||||||
this.promptInfo = null;
|
this.promptInfo = null;
|
||||||
|
|
||||||
if (!this.promptId) return;
|
if (!this.promptId) return;
|
||||||
|
|
||||||
const client = new GraphQLClient('/api/website/graphql');
|
|
||||||
const sdk = getSdk(client);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { promptInfo } = await sdk.GetPromptInfo({
|
const { promptInfo } = await sdk.GetPromptInfo({
|
||||||
promptId: this.promptId,
|
promptId: this.promptId,
|
||||||
|
@ -262,12 +158,13 @@ export default class AuthenticatePromptApp extends Vue {
|
||||||
await this.loadPromptInfo();
|
await this.loadPromptInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack() {
|
loginBack() {
|
||||||
this.step -= 1;
|
this.step = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
beginLogin() {
|
overviewNext() {
|
||||||
this.step = 2;
|
this.step = 2;
|
||||||
|
this.loginWindow.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
18
website/src/pages/authenticate-prompt/sdk.ts
Normal file
18
website/src/pages/authenticate-prompt/sdk.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { ClientError, GraphQLClient } from 'graphql-request';
|
||||||
|
import { getSdk } from '@/graphql/generated';
|
||||||
|
import { GraphQLError } from 'graphql-request/dist/types';
|
||||||
|
|
||||||
|
const client = new GraphQLClient('/api/website/graphql');
|
||||||
|
export const sdk = getSdk(client);
|
||||||
|
|
||||||
|
export interface GraphQLErrorFull extends GraphQLError {
|
||||||
|
extensions?: {
|
||||||
|
code?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasErrorCode(error: unknown, code: string): boolean {
|
||||||
|
if (!(error instanceof ClientError)) return false;
|
||||||
|
return error.response.errors
|
||||||
|
?.some((resError: GraphQLErrorFull) => resError.extensions?.code === code) ?? false;
|
||||||
|
}
|
17
website/src/types.ts
Normal file
17
website/src/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
export enum StudentsMode {
|
||||||
|
None = 'none',
|
||||||
|
One = 'one',
|
||||||
|
Many = 'many',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PromptInfo {
|
||||||
|
id: string;
|
||||||
|
scopes: string[];
|
||||||
|
studentsMode: StudentsMode;
|
||||||
|
application: {
|
||||||
|
name: string;
|
||||||
|
iconUrl: string | null;
|
||||||
|
iconColor: string;
|
||||||
|
verified: boolean;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue