Create website
This commit is contained in:
parent
85c94b3516
commit
a4419528a0
27 changed files with 13652 additions and 0 deletions
|
@ -3,6 +3,7 @@
|
|||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/backend/backend.iml" filepath="$PROJECT_DIR$/backend/backend.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/website/website.iml" filepath="$PROJECT_DIR$/website/website.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -19,6 +19,9 @@ export default class Application extends BaseEntity {
|
|||
@Column()
|
||||
public iconUrl!: string | null;
|
||||
|
||||
@Column()
|
||||
public iconColor!: string;
|
||||
|
||||
@Column()
|
||||
public verified!: boolean;
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ export default function registerAuthorize(server: MyFastifyInstance): void {
|
|||
throw new ScopeError(`Unknown scope ${scope}`);
|
||||
}
|
||||
});
|
||||
// TODO: Check if user requests student scopes according to students_mode
|
||||
|
||||
const promptId = nanoid(12);
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ export default class PromptInfoApplication {
|
|||
})
|
||||
public iconUrl!: string | null;
|
||||
|
||||
@Field(() => String)
|
||||
public iconColor!: string;
|
||||
|
||||
@Field(() => Boolean)
|
||||
public verified!: boolean;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ export default class PromptInfoResolver implements ResolverInterface<PromptInfo>
|
|||
return {
|
||||
name: application.name,
|
||||
iconUrl: application.iconUrl,
|
||||
iconColor: application.iconColor,
|
||||
verified: application.verified,
|
||||
};
|
||||
}
|
||||
|
|
3
website/.browserslistrc
Normal file
3
website/.browserslistrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
7
website/.editorconfig
Normal file
7
website/.editorconfig
Normal file
|
@ -0,0 +1,7 @@
|
|||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
18
website/.eslintrc.js
Normal file
18
website/.eslintrc.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'@vue/airbnb',
|
||||
'@vue/typescript/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
},
|
||||
};
|
22
website/.gitignore
vendored
Normal file
22
website/.gitignore
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
1
website/.npmrc
Normal file
1
website/.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
shamefully-hoist=true
|
24
website/README.md
Normal file
24
website/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# @wulkanowy/bridge-website
|
||||
|
||||
## Project setup
|
||||
```
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
pnpm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
website/babel.config.js
Normal file
5
website/babel.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset',
|
||||
],
|
||||
};
|
13159
website/package-lock.json
generated
Normal file
13159
website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
36
website/package.json
Normal file
36
website/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@wulkanowy/bridge-website",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vuetify": "^2.2.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-airbnb": "^5.0.2",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"typescript": "~3.9.3",
|
||||
"vue-cli-plugin-vuetify": "~2.0.9",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuetify-loader": "^1.3.0"
|
||||
}
|
||||
}
|
BIN
website/public/favicon.ico
Normal file
BIN
website/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
19
website/public/index.html
Normal file
19
website/public/index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>This website doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
BIN
website/src/assets/logo.png
Normal file
BIN
website/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
1
website/src/assets/logo.svg
Normal file
1
website/src/assets/logo.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
|
After Width: | Height: | Size: 539 B |
228
website/src/pages/authenticate-prompt/app.vue
Normal file
228
website/src/pages/authenticate-prompt/app.vue
Normal file
|
@ -0,0 +1,228 @@
|
|||
<template>
|
||||
<v-app class="authenticate-prompt-app">
|
||||
<div>
|
||||
<div class="text-h4 text-center mt-8">
|
||||
<span class="primary--text">Wulkanowy</span> Bridge
|
||||
</div>
|
||||
</div>
|
||||
<v-main class="px-4">
|
||||
<v-sheet max-width="500" class="mx-auto mt-16" color="transparent">
|
||||
<v-alert type="error" text v-if="promptInfoError">
|
||||
Nie udało się wczytać danych
|
||||
<template #append>
|
||||
<v-btn text color="error" @click="loadPromptInfo">
|
||||
Spróbuj ponownie
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-alert>
|
||||
<div class="d-flex align-center justify-center mt-16 mb-16" v-else-if="!promptInfo">
|
||||
<v-progress-circular indeterminate :size="96" color="primary" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="pb-1 text--secondary">
|
||||
Krok <span class="primary--text">{{ step }}/5</span>
|
||||
</div>
|
||||
<v-card outlined>
|
||||
<div class="d-flex justify-center mn-16 avatar__wrapper">
|
||||
<v-badge
|
||||
:color="promptInfo.application.verified ? 'green' : 'grey'"
|
||||
offset-x="64"
|
||||
offset-y="16"
|
||||
bottom
|
||||
:content="promptInfo.application.verified ? 'Zweryfikowana' : 'Niezweryfikowana'"
|
||||
:value="step === 1"
|
||||
>
|
||||
<transition name="scale">
|
||||
<v-sheet
|
||||
v-if="step === 1"
|
||||
width="128"
|
||||
height="128"
|
||||
class="avatar-sheet mx-4 overflow-hidden"
|
||||
outlined
|
||||
>
|
||||
<v-sheet
|
||||
class="fill-height d-flex align-center justify-center"
|
||||
:color="promptInfo.application.iconColor"
|
||||
>
|
||||
<v-img
|
||||
:src="promptInfo.application.iconUrl"
|
||||
width="80"
|
||||
height="80"
|
||||
aspect-ratio="1"
|
||||
contain
|
||||
>
|
||||
<template v-slot:placeholder>
|
||||
<div class="fill-height d-flex align-center justify-center">
|
||||
<v-icon :size="80">
|
||||
mdi-help
|
||||
</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
</v-img>
|
||||
</v-sheet>
|
||||
</v-sheet>
|
||||
</transition>
|
||||
</v-badge>
|
||||
</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>
|
||||
<v-divider />
|
||||
</div>
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" text outlined>
|
||||
Odmów
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn color="primary">
|
||||
Zaloguj się
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-sheet>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.authenticate-prompt-app {
|
||||
background-color: #f7f7f7 !important;
|
||||
|
||||
.avatar-sheet {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.fill-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar__wrapper {
|
||||
position: absolute;
|
||||
top: -64px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scale-enter-active, .scale-leave-active {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.scale-enter, .scale-leave-to {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
export enum StudentsMode {
|
||||
None = 'none',
|
||||
One = 'one',
|
||||
Many = 'many',
|
||||
}
|
||||
|
||||
export interface PromptInfo {
|
||||
scopes: string[];
|
||||
studentsMode: StudentsMode;
|
||||
application: {
|
||||
name: string;
|
||||
iconUrl: string | null;
|
||||
iconColor: string;
|
||||
verified: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@Component({
|
||||
name: 'AuthenticatePromptApp',
|
||||
})
|
||||
export default class AuthenticatePromptApp extends Vue {
|
||||
promptInfo: PromptInfo | null = null;
|
||||
|
||||
promptInfoError = false;
|
||||
|
||||
step = 1;
|
||||
|
||||
readonly scopeDescriptions: Record<string, {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
icon: string;
|
||||
}> = {
|
||||
timetable: {
|
||||
title: 'Plan lekcji',
|
||||
icon: 'mdi-timetable',
|
||||
},
|
||||
grades: {
|
||||
title: 'Oceny i punkty',
|
||||
subtitle: 'Oceny cząstkowe, końcowe, opisowe oraz punkty',
|
||||
icon: 'mdi-numeric-6-box-multiple-outline',
|
||||
},
|
||||
notes: {
|
||||
title: 'Uwagi',
|
||||
icon: 'mdi-note-text-outline',
|
||||
},
|
||||
achievements: {
|
||||
title: 'Osiągnięcia',
|
||||
icon: 'mdi-trophy-outline',
|
||||
},
|
||||
}
|
||||
|
||||
get scopeItems() {
|
||||
if (this.promptInfo === null) return undefined;
|
||||
return this.promptInfo.scopes.map((key: string) => ({
|
||||
key,
|
||||
...this.scopeDescriptions[key],
|
||||
}));
|
||||
}
|
||||
|
||||
async loadPromptInfo() {
|
||||
this.promptInfoError = false;
|
||||
this.promptInfo = null;
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
if (Math.random() < 0.5) {
|
||||
this.promptInfoError = true;
|
||||
} else {
|
||||
this.promptInfo = {
|
||||
scopes: ['grades', 'timetable', 'notes', 'achievements'],
|
||||
studentsMode: StudentsMode.Many,
|
||||
application: {
|
||||
iconColor: '#f00',
|
||||
iconUrl: null,
|
||||
name: 'Not a fancy app',
|
||||
verified: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async created() {
|
||||
await this.loadPromptInfo();
|
||||
}
|
||||
}
|
||||
</script>
|
10
website/src/pages/authenticate-prompt/main.ts
Normal file
10
website/src/pages/authenticate-prompt/main.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import Vue from 'vue';
|
||||
import vuetify from '@/plugins/vuetify';
|
||||
import AuthenticatePromptApp from './app.vue';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
vuetify,
|
||||
render: (h) => h(AuthenticatePromptApp),
|
||||
}).$mount('#app');
|
25
website/src/plugins/vuetify.ts
Normal file
25
website/src/plugins/vuetify.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import Vue from 'vue';
|
||||
import Vuetify from 'vuetify/lib/framework';
|
||||
import pl from 'vuetify/src/locale/pl';
|
||||
|
||||
Vue.use(Vuetify);
|
||||
|
||||
export default new Vuetify({
|
||||
theme: {
|
||||
options: {
|
||||
customProperties: true,
|
||||
},
|
||||
themes: {
|
||||
light: {
|
||||
primary: '#d32f2f',
|
||||
secondary: '#d32f2f',
|
||||
accent: '#d32f2f',
|
||||
error: '#ff5722',
|
||||
},
|
||||
},
|
||||
},
|
||||
lang: {
|
||||
locales: { pl },
|
||||
current: 'pl',
|
||||
},
|
||||
});
|
13
website/src/shims-tsx.d.ts
vendored
Normal file
13
website/src/shims-tsx.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Vue, { VNode } from 'vue';
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
interface Element extends VNode {}
|
||||
// tslint:disable no-empty-interface
|
||||
interface ElementClass extends Vue {}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
5
website/src/shims-vue.d.ts
vendored
Normal file
5
website/src/shims-vue.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare module '*.vue' {
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue;
|
||||
}
|
5
website/src/shims-vuetify.d.ts
vendored
Normal file
5
website/src/shims-vuetify.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare module 'vuetify/lib/framework' {
|
||||
import Vuetify from 'vuetify';
|
||||
|
||||
export default Vuetify;
|
||||
}
|
40
website/tsconfig.json
Normal file
40
website/tsconfig.json
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
13
website/vue.config.js
Normal file
13
website/vue.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
transpileDependencies: [
|
||||
'vuetify',
|
||||
],
|
||||
pages: {
|
||||
'authenticate-prompt': {
|
||||
entry: 'src/pages/authenticate-prompt/main.ts',
|
||||
template: 'public/index.html',
|
||||
filename: 'authenticate-prompt.html',
|
||||
title: 'Authorize application | Wulkanowy Bridge',
|
||||
},
|
||||
},
|
||||
};
|
9
website/website.iml
Normal file
9
website/website.iml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
Loading…
Reference in a new issue