parent
fcbf3ee796
commit
73847022e2
@ -11,7 +11,7 @@ Files: .browserslistrc .dockerignore .eslintrc.js .gitignore
|
||||
Copyright: WTF Kooperative eG <https://wtf-eg.de/>
|
||||
License: AGPL-3.0-or-later
|
||||
|
||||
Files: src/assets/img/wtf_logo*
|
||||
Files: src/assets/img/wtf*
|
||||
Copyright: WTF Kooperative eG <https://wtf-eg.de/>
|
||||
License: LicenseRef-WTF
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -15,6 +15,7 @@
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^5.0.1",
|
||||
"bootstrap-icons": "^1.5.0",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
@ -2854,6 +2855,15 @@
|
||||
"@popperjs/core": "^2.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap-icons": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.5.0.tgz",
|
||||
"integrity": "sha512-44feMc7DE1Ccpsas/1wioN8ewFJNquvi5FewA06wLnqct7CwMdGDVy41ieHaacogzDqLfG8nADIvMNp9e4bfbA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz?cache=0&sync_timestamp=1614010713935&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrace-expansion%2Fdownload%2Fbrace-expansion-1.1.11.tgz",
|
||||
@ -17155,6 +17165,12 @@
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"bootstrap-icons": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.5.0.tgz",
|
||||
"integrity": "sha512-44feMc7DE1Ccpsas/1wioN8ewFJNquvi5FewA06wLnqct7CwMdGDVy41ieHaacogzDqLfG8nADIvMNp9e4bfbA==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz?cache=0&sync_timestamp=1614010713935&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrace-expansion%2Fdownload%2Fbrace-expansion-1.1.11.tgz",
|
||||
|
@ -14,6 +14,7 @@
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^5.0.1",
|
||||
"bootstrap-icons": "^1.5.0",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
|
@ -1 +0,0 @@
|
||||
SPDX-License-Identifier: MIT
|
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
|
||||
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 548 B |
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-lg" viewBox="0 0 16 16">
|
||||
<path d="M8 0a1 1 0 0 1 1 1v6h6a1 1 0 1 1 0 2H9v6a1 1 0 1 1-2 0V9H1a1 1 0 0 1 0-2h6V1a1 1 0 0 1 1-1z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 238 B |
@ -1 +0,0 @@
|
||||
SPDX-License-Identifier: MIT
|
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
|
||||
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 331 B |
@ -1 +0,0 @@
|
||||
SPDX-License-Identifier: MIT
|
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 573 B |
@ -1 +0,0 @@
|
||||
SPDX-License-Identifier: MIT
|
@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
<Footer />
|
||||
</template>
|
||||
<script>
|
||||
import Footer from '@/components/Footer.vue'
|
||||
import Navbar from '@/components/Navbar.vue'
|
||||
import Footer from '@/components/Footer'
|
||||
import Navbar from '@/components/Navbar'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
@ -7,6 +7,13 @@
|
||||
@import "variables";
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
|
||||
.bg-wtf {
|
||||
background-image: url(../assets/img/wtf-header-bg.jpg);
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
BIN
src/assets/img/wtf-header-bg.jpg
Normal file
BIN
src/assets/img/wtf-header-bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
@ -14,3 +14,5 @@ $body-bg: $gray-100;
|
||||
|
||||
$link-decoration: none;
|
||||
$link-hover-decoration: underline;
|
||||
|
||||
$spinner-animation-speed: 1s;
|
||||
|
@ -27,10 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
aria-label="Hinzufügen"
|
||||
@click="addResult()"
|
||||
>
|
||||
<img
|
||||
src="/img/bootstrap-icons-1.5.0/plus-lg.svg"
|
||||
alt="Hinzufügen Icon"
|
||||
/>
|
||||
<i clas="bi-plus-lg"></i>
|
||||
Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
37
src/components/Avatar.vue
Normal file
37
src/components/Avatar.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="avatar bg-dark">
|
||||
{{ avatarLetters }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: String
|
||||
},
|
||||
computed: {
|
||||
avatarLetters() {
|
||||
return this.name.substr(0, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.avatar {
|
||||
align-items: center;
|
||||
border-radius: .25rem;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
height: 2.5rem;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
}
|
||||
</style>
|
@ -26,59 +26,59 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div
|
||||
class="collapse navbar-collapse"
|
||||
class="collapse navbar-collapse d-lg-flex"
|
||||
:class="{ show: showMobileNav }"
|
||||
id="navbarSupportedContent"
|
||||
>
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<ul class="navbar-nav mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{ path: `/s/search` }"
|
||||
active-class="active"
|
||||
>Suche</router-link
|
||||
>
|
||||
>Suche</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{ path: `/s/profile/${currentUserId}` }"
|
||||
active-class="active"
|
||||
>Mein Profil</router-link
|
||||
>
|
||||
>Mein Profil</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{ path: `/s/profile-edit` }"
|
||||
active-class="active"
|
||||
>Bearbeiten</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="btn btn-outline-danger" @click="logout()">
|
||||
Logout
|
||||
</button>
|
||||
>Bearbeiten</router-link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="d-flex" @submit.prevent="searchRedirect()">
|
||||
<form class="flex-grow-1 d-flex ms-lg-3 me-lg-4 mb-3 mb-lg-0" @submit.prevent="searchRedirect()">
|
||||
<input
|
||||
class="form-control me-2"
|
||||
class="form-control bg-white me-2"
|
||||
v-model="searchText"
|
||||
type="search"
|
||||
placeholder="Profile durchsuchen"
|
||||
aria-label="Search"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
<img
|
||||
src="/img/bootstrap-icons-1.5.0/search.svg"
|
||||
alt="Suche Icon"
|
||||
/>
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</form>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<button class="btn btn-danger w-100" @click="logout()">
|
||||
<i class="bi bi-box-arrow-right"></i>
|
||||
Logout
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -82,10 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
aria-label="Löschen"
|
||||
@click="$emit('removeValue', value[type].name)"
|
||||
>
|
||||
<img
|
||||
src="/img/bootstrap-icons-1.5.0/trash.svg"
|
||||
alt="Löschen Icon"
|
||||
/>
|
||||
<i class="bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
54
src/components/SearchResult.vue
Normal file
54
src/components/SearchResult.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<router-link
|
||||
class="text-decoration-none d-flex"
|
||||
:to="{ path: `/s/profile/${profile.user_id}` }"
|
||||
>
|
||||
<div class="card w-100">
|
||||
<div class="card-body d-flex">
|
||||
<div class="d-flex align-items-center justify-content-center me-3">
|
||||
<Avatar :name="profile.nickname"/>
|
||||
</div>
|
||||
<div class="text-body">
|
||||
<h5 class="card-title mb-1 lh-1">
|
||||
{{ profile.nickname}}
|
||||
<span v-if="profile.pronouns"> ({{ profile.pronouns }})</span>
|
||||
</h5>
|
||||
<small
|
||||
class="card-text lh-1 text-dark"
|
||||
v-if="profile.skills && profile.skills.length > 0"
|
||||
>
|
||||
Top-Fähigkeiten: {{ topSkills }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
<script>
|
||||
import Avatar from '@/components/Avatar'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Avatar,
|
||||
},
|
||||
props: {
|
||||
profile: Object
|
||||
},
|
||||
computed: {
|
||||
topSkills() {
|
||||
return this.profile.skills.slice(0, 5).map(s => s.skill.name).join(', ')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.card:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
</style>
|
9
src/components/Spinner.vue
Normal file
9
src/components/Spinner.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="spinner-grow" role="status"></div>
|
||||
</template>
|
@ -7,6 +7,7 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from '@/store'
|
||||
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||
import './assets/global.scss'
|
||||
|
||||
const app = createApp(App)
|
||||
|
@ -4,12 +4,17 @@
|
||||
|
||||
import { createStore } from 'vuex'
|
||||
|
||||
import search from './search'
|
||||
|
||||
const localStorageKeys = {
|
||||
currentUserId: 'ki_current_user_id',
|
||||
token: 'ki_token',
|
||||
}
|
||||
|
||||
export default createStore({
|
||||
modules: {
|
||||
search,
|
||||
},
|
||||
state() {
|
||||
return {
|
||||
currentUserId: JSON.parse(localStorage.getItem(localStorageKeys.currentUserId)),
|
100
src/store/search.js
Normal file
100
src/store/search.js
Normal file
@ -0,0 +1,100 @@
|
||||
// SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state() {
|
||||
return {
|
||||
searching: false,
|
||||
showSpinner: false,
|
||||
searched: false,
|
||||
profiles: [],
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
query: {
|
||||
search: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setSearching(state, searching) {
|
||||
state.searching = searching
|
||||
},
|
||||
showSpinner(state) {
|
||||
state.showSpinner = true
|
||||
},
|
||||
hideSpinner(state) {
|
||||
state.showSpinner = false
|
||||
},
|
||||
clearProfiles(state) {
|
||||
state.profiles = []
|
||||
},
|
||||
setProfiles(state, profiles) {
|
||||
state.profiles = profiles
|
||||
},
|
||||
setError(state, error) {
|
||||
state.error = error
|
||||
},
|
||||
setErrorMessage(state, errorMessage) {
|
||||
state.errorMessage = errorMessage
|
||||
},
|
||||
setQuerySearch(state, search) {
|
||||
state.query.search = search
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async search({state, commit, rootState}) {
|
||||
if (state.searching) {
|
||||
return
|
||||
}
|
||||
|
||||
commit('setSearching', true)
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
commit('showSpinner')
|
||||
commit('clearProfiles')
|
||||
}, 100)
|
||||
|
||||
commit('setError', false)
|
||||
commit('setErrorMessage', '')
|
||||
|
||||
const url = new URL(`${window.ki.apiUrl}/users/profiles`)
|
||||
|
||||
if (state.query.search) {
|
||||
url.searchParams.append('search', state.query.search)
|
||||
}
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${rootState.token}`,
|
||||
}
|
||||
|
||||
let response
|
||||
|
||||
try {
|
||||
response = await fetch(url, {headers})
|
||||
} catch {
|
||||
commit('setError', true)
|
||||
commit('clearProfiles')
|
||||
commit('setSearching', false)
|
||||
commit('hideSpinner')
|
||||
return
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
commit('setError', true)
|
||||
commit('clearProfiles')
|
||||
commit('setSearching', false)
|
||||
commit('hideSpinner')
|
||||
return
|
||||
}
|
||||
|
||||
const responseData = await response.json()
|
||||
commit('setProfiles', responseData.profiles)
|
||||
commit('setSearching', false)
|
||||
commit('hideSpinner')
|
||||
}
|
||||
}
|
||||
}
|
@ -5,46 +5,51 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="container pt-5">
|
||||
<div class="text-center mb-5">
|
||||
<img class="wtf-logo wtf-logo--index" src="@/assets/img/wtf_logo.svg">
|
||||
<h1>Kompetenzinventar</h1>
|
||||
<div class="bg-wtf">
|
||||
<div class="container pt-5">
|
||||
<div class="text-center mb-5">
|
||||
<img class="wtf-logo wtf-logo--index" src="@/assets/img/wtf_logo.svg">
|
||||
<h1 class="text-white">Kompetenzinventar</h1>
|
||||
</div>
|
||||
<form @submit.prevent="submitLogin()" class="card bg-white login-form">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="exampleInputusername1" class="form-label" >
|
||||
WTF-Benutzername:
|
||||
</label>
|
||||
<input
|
||||
type="username"
|
||||
class="form-control"
|
||||
id="exampleInputusername1"
|
||||
v-model="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="exampleInputPassword1" class="form-label">Passwort:</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="exampleInputPassword1"
|
||||
v-model="password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
<a class="btn btn-link" href="https://resetpw.wtf-eg.de/">Passwort vergessen?</a>
|
||||
<div
|
||||
class="alert alert-danger mt-3 mb-0"
|
||||
role="alert"
|
||||
v-if="showErrorMessage"
|
||||
>
|
||||
Dein Benutzername oder Passwort ist falsch.<br>Versuche es noch einmal.
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form @submit.prevent="submitLogin()" class="bg-white p-3 login-form">
|
||||
<div class="mb-3">
|
||||
<label for="exampleInputusername1" class="form-label" >
|
||||
WTF-Benutzername:
|
||||
</label>
|
||||
<input
|
||||
type="username"
|
||||
class="form-control"
|
||||
id="exampleInputusername1"
|
||||
v-model="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="exampleInputPassword1" class="form-label">Passwort:</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="exampleInputPassword1"
|
||||
v-model="password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
<a class="btn btn-link" href="https://resetpw.wtf-eg.de/">Passwort vergessen?</a>
|
||||
<div
|
||||
class="alert alert-danger mt-3 mb-0"
|
||||
role="alert"
|
||||
v-if="showErrorMessage"
|
||||
>
|
||||
Dein Benutzername oder Passwort ist falsch.<br>Versuche es noch einmal.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RequestMixin from "@/mixins/request.mixin"
|
||||
|
||||
@ -62,6 +67,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
@ -5,88 +5,129 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>Suche</h1>
|
||||
<form @submit.prevent="submitSearch()">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="searchText"
|
||||
v-model="searchText"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-primary mb-4">
|
||||
Suche starten
|
||||
</button>
|
||||
<div class="content">
|
||||
<div class="bg-wtf text-white pt-3 pb-4">
|
||||
<div class="container">
|
||||
<div class="fs-3 text-center lh-1 mb-3">Finde WTF Member</div>
|
||||
<div class="card mx-auto bg-white">
|
||||
<div class="card-body">
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<fieldset class="d-flex" :disabled="searching">
|
||||
<div class="flex-grow-1 me-3">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="searchText"
|
||||
v-model="searchText"
|
||||
placeholder="Nick, Fähigkeit, Sprache"
|
||||
ref="searchTextInput"
|
||||
/>
|
||||
</div>
|
||||
<div class="">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi-search"></i>
|
||||
<span class="d-none d-md-inline"> Suchen</span>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
class="alert alert-danger mb-4 mt-4"
|
||||
role="alert"
|
||||
v-if="showErrorMessage"
|
||||
>
|
||||
Bei der Suche ist ein Fehler aufgetreten
|
||||
</div>
|
||||
<div v-if="searchTotal == 0">
|
||||
Es wurde kein Suchergebnis gefunden.
|
||||
<p v-if="searchText !== ''">Probiere eine andere Suche.</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="row">
|
||||
<div
|
||||
class="col-4 p-2"
|
||||
v-for="result in searchResults"
|
||||
:key="result.user_id"
|
||||
>
|
||||
<router-link
|
||||
class="text-decoration-none"
|
||||
:to="{ path: `/s/profile/${result.user_id}` }"
|
||||
>
|
||||
<div class="card search-card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
{{ result.nickname}}
|
||||
<span v-if="result.pronouns"> ({{ result.pronouns }})</span>
|
||||
</h5>
|
||||
<p
|
||||
class="card-text"
|
||||
v-if="result.skills && result.skills.length > 0"
|
||||
>
|
||||
Fähigkeiten:
|
||||
<span v-for="(skill, index) in result.skills" :key="index"
|
||||
>
|
||||
{{ skill.skill.name}}<span v-if="index != result.skills.length - 1">, </span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="container pt-4 pb-3">
|
||||
<div class="text-center" v-if="showSpinner">
|
||||
<Spinner />
|
||||
</div>
|
||||
<div
|
||||
class="fs-2 text-danger text-center"
|
||||
role="alert"
|
||||
v-if="error"
|
||||
>
|
||||
<div class="fs-1 mb-3">Kernel panic :/</div>
|
||||
Bei der Suche ist ein Fehler aufgetreten.
|
||||
</div>
|
||||
<div v-else-if="showNoResults" class="fs-2 text-black-50 text-center">
|
||||
<div class="fs-1 mb-3">nullptr :/</div>
|
||||
Es wurde kein Suchergebnis gefunden.
|
||||
<p v-if="searchText !== ''">Probiere eine andere Suche.</p>
|
||||
</div>
|
||||
<div v-else-if="showResults">
|
||||
<SearchResult
|
||||
v-for="profile in profiles"
|
||||
:key="profile.user_id"
|
||||
class="mb-3"
|
||||
:profile="profile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RequestMixin from "@/mixins/request.mixin";
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import SearchResult from '@/components/SearchResult'
|
||||
import Spinner from '@/components/Spinner'
|
||||
|
||||
export default {
|
||||
name: "Search",
|
||||
mixins: [RequestMixin],
|
||||
data() {
|
||||
return {
|
||||
showErrorMessage: false,
|
||||
searchText: "",
|
||||
searchResults: null,
|
||||
searchTotal: 0,
|
||||
};
|
||||
name: 'Search',
|
||||
components: {
|
||||
SearchResult,
|
||||
Spinner,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
searching: state => state.search.searching,
|
||||
profiles: state => state.search.profiles,
|
||||
error: state => state.search.error,
|
||||
showSpinner: state => state.search.showSpinner,
|
||||
}),
|
||||
searchText: {
|
||||
get() {
|
||||
return this.$store.state.search.query.search
|
||||
},
|
||||
set(text) {
|
||||
this.$store.commit('search/setQuerySearch', text)
|
||||
}
|
||||
},
|
||||
showNoResults() {
|
||||
return !this.searching && (!this.profiles || this.profiles.length === 0)
|
||||
},
|
||||
showResults() {
|
||||
return !this.error && this.profiles && this.profiles.length > 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searching(value) {
|
||||
if (value) {
|
||||
if (this.$refs.searchTextInput) {
|
||||
this.$refs.searchTextInput.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
this.$store.dispatch('search/search')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$route.query.query) this.searchText = this.$route.query.query;
|
||||
this.submitSearch();
|
||||
},
|
||||
if (this.$route.query.query !== undefined) {
|
||||
this.searchText = this.$route.query.query
|
||||
this.$store.commit('search/clearProfiles')
|
||||
}
|
||||
|
||||
this.$store.dispatch('search/search')
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user