Zdockerovani, sjednoceni marginu, drobnejsi upravy
This commit is contained in:
+22
-22
@@ -1,27 +1,27 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
|
#
|
||||||
db:
|
# db:
|
||||||
image: mariadb:10.11
|
# image: mariadb:10.11
|
||||||
ports:
|
# ports:
|
||||||
- 3306:3306
|
# - 3306:3306
|
||||||
environment:
|
# environment:
|
||||||
- MYSQL_USER=root
|
# - MYSQL_USER=root
|
||||||
- MYSQL_DATABASE=ull_tracker
|
# - MYSQL_DATABASE=ull_tracker
|
||||||
- MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=true
|
# - MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=true
|
||||||
volumes:
|
# volumes:
|
||||||
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
# - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||||
- db-data:/var/lib/mysql:rw
|
# - db-data:/var/lib/mysql:rw
|
||||||
api:
|
# api:
|
||||||
build:
|
# build:
|
||||||
context: ../api
|
# context: ../api
|
||||||
ports:
|
# ports:
|
||||||
- 8000:8000
|
# - 8000:8000
|
||||||
volumes:
|
# volumes:
|
||||||
- ../api:/app
|
# - ../api:/app
|
||||||
environment:
|
# environment:
|
||||||
TZ: "Europe/Prague"
|
# TZ: "Europe/Prague"
|
||||||
# command: ls -l /app
|
## command: ls -l /app
|
||||||
|
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||||
devServer: {
|
devServer: {
|
||||||
// https: true
|
// https: true
|
||||||
open: true // opens browser window automatically
|
open: false // opens browser window automatically
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
||||||
|
|||||||
+2
-4
@@ -19,7 +19,7 @@ api.interceptors.request.use((config) => {
|
|||||||
config.headers['Authorization'] = `Bearer ${token}`
|
config.headers['Authorization'] = `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.url === '/refresh') {
|
if (['/refresh', '/login'].includes(config.url)) {
|
||||||
delete config.headers['Authorization'];
|
delete config.headers['Authorization'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,11 +31,9 @@ api.interceptors.response.use((response) => {
|
|||||||
return response
|
return response
|
||||||
}, async function (error) {
|
}, async function (error) {
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
if (error.response.status === 401 && !originalRequest._retry) {
|
if (error.response.status === 401 && !['/refresh', '/login'].includes(originalRequest.url)) {
|
||||||
originalRequest._retry = true;
|
|
||||||
const newToken = await useUserStore().refreshAccessToken().catch(err => {
|
const newToken = await useUserStore().refreshAccessToken().catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
useRouter().push({to: "login"});
|
|
||||||
return Promise.reject("Logged out");
|
return Promise.reject("Logged out");
|
||||||
})
|
})
|
||||||
api.defaults.headers.common['Authorization'] = 'Bearer ' + newToken;
|
api.defaults.headers.common['Authorization'] = 'Bearer ' + newToken;
|
||||||
|
|||||||
+15
-1
@@ -1,10 +1,24 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {randomInt} from "src/composables/random";
|
import {randomInt} from "src/composables/random";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
itemsCountMin: {
|
||||||
|
type: Number,
|
||||||
|
required:false,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
itemsCountMax: {
|
||||||
|
type: Number,
|
||||||
|
required:false,
|
||||||
|
default: 5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-list bordered separator>
|
<q-list bordered separator>
|
||||||
<q-item v-ripple v-for="i in randomInt(2, 5)" :key="i">
|
<q-item v-ripple v-for="i in randomInt(itemsCountMin, itemsCountMax)" :key="i">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-skeleton type="QAvatar" />
|
<q-skeleton type="QAvatar" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
@@ -42,12 +42,12 @@ const fullwidthBtn = computed(() => $q.screen.width <= $q.screen.sizes.md);
|
|||||||
<q-form @submit.prevent="saveForm" ref="form">
|
<q-form @submit.prevent="saveForm" ref="form">
|
||||||
<slot name="default"/>
|
<slot name="default"/>
|
||||||
|
|
||||||
<slot name="saveButton" />
|
<div class="text-right">
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
:class="{'q-my-lg': true, 'btn--fullwidth': fullwidthBtn}"
|
size="lg"
|
||||||
|
:class="{'q-my-lg': true, 'q-mx-lg': true, 'btn--fullwidth': fullwidthBtn}"
|
||||||
v-if="saveButton"
|
v-if="saveButton"
|
||||||
:icon="props.saveButton.icon"
|
:icon="props.saveButton.icon"
|
||||||
:loading="props.isRequestPending"
|
:loading="props.isRequestPending"
|
||||||
@@ -58,6 +58,7 @@ const fullwidthBtn = computed(() => $q.screen.width <= $q.screen.sizes.md);
|
|||||||
Loading...
|
Loading...
|
||||||
</template>
|
</template>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
</div>
|
||||||
</q-form>
|
</q-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import {onMounted, reactive} from "vue";
|
|||||||
import FormWrapper from "components/FormWrapper.vue";
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
import {setDefaultData} from "src/composables/form/formHandling";
|
import {setDefaultData} from "src/composables/form/formHandling";
|
||||||
import {defaultValues, isRequestPending} from "src/composables/form/props";
|
import {defaultValues, isRequestPending} from "src/composables/form/props";
|
||||||
import {isRequired} from "src/composables/form/rules";
|
import {isRequired, maxLength, minLength} from "src/composables/form/rules";
|
||||||
|
import {commonProps} from "src/composables/inputs";
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
id: null,
|
id: null,
|
||||||
callSign: "",
|
callSign: "",
|
||||||
model: "",
|
model: "",
|
||||||
|
seats: null,
|
||||||
manufacturer: "",
|
manufacturer: "",
|
||||||
description: "",
|
description: "",
|
||||||
photo: null,
|
photo: null,
|
||||||
@@ -23,8 +25,13 @@ const props = defineProps({
|
|||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
callSign: [
|
callSign: [
|
||||||
v => isRequired(v)
|
isRequired,
|
||||||
|
minLength(4),
|
||||||
|
maxLength(8)
|
||||||
],
|
],
|
||||||
|
seats: [
|
||||||
|
isNumber
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -35,14 +42,49 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper v-model="formData" :is-request-pending="isRequestPending">
|
<FormWrapper v-model="formData" :is-request-pending="isRequestPending">
|
||||||
<q-input type="text" :rules="rules.callSign" v-model="formData.callSign" label="Volací značka"/>
|
|
||||||
<q-input type="text" v-model="formData.model" label="Model"/>
|
|
||||||
<q-input type="text" v-model="formData.manufacturer" label="Výrobce"/>
|
|
||||||
<q-input type="textarea" v-model="formData.description" label="Popis" />
|
|
||||||
|
|
||||||
|
|
||||||
{{ props.defaultValues.photoUrl }}
|
|
||||||
<q-img :src="props.defaultValues.photoUrl" v-if="props.defaultValues.photoUrl"/>
|
<q-img :src="props.defaultValues.photoUrl" v-if="props.defaultValues.photoUrl"/>
|
||||||
<q-file v-model="formData.photo" label="Fotografie" />
|
|
||||||
|
<q-input
|
||||||
|
type="text"
|
||||||
|
:rules="rules.callSign"
|
||||||
|
v-model="formData.callSign"
|
||||||
|
label="Volací značka"
|
||||||
|
v-bind="commonProps"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
type="text"
|
||||||
|
v-model="formData.manufacturer"
|
||||||
|
label="Výrobce"
|
||||||
|
v-bind="commonProps"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
type="text"
|
||||||
|
v-model="formData.model"
|
||||||
|
label="Model"
|
||||||
|
v-bind="commonProps"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
type="number"
|
||||||
|
v-model="formData.seats"
|
||||||
|
label="Počet míst"
|
||||||
|
min="1"
|
||||||
|
:rules="rules.seats"
|
||||||
|
v-bind="commonProps"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="formData.description"
|
||||||
|
label="Popis"
|
||||||
|
v-bind="commonProps"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-file v-model="formData.photo" label="Fotografie" v-bind="commonProps">
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon name="upload"/>
|
||||||
|
</template>
|
||||||
|
</q-file>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import {randomInt} from "src/composables/random";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-list bordered separator>
|
|
||||||
<q-item v-ripple v-for="i in randomInt(2, 5)" :key="i">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-skeleton type="QAvatar"/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-skeleton :width="randomInt(10,25)+'%'"/>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</template>
|
|
||||||
@@ -23,7 +23,7 @@ const props = defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-list bordered separator class="q-mx-md q-my-md">
|
<q-list bordered separator>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="flight of items" :key="flight"
|
v-for="flight of items" :key="flight"
|
||||||
ripple
|
ripple
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<em>POI list skeleton</em>
|
|
||||||
</template>
|
|
||||||
@@ -1 +1,20 @@
|
|||||||
export const isRequired = (v) => !!v || "Required"
|
export const isRequired = (val) => !!val || "Toto pole je povinné";
|
||||||
|
export const isValidEmail = (email) => {
|
||||||
|
const patternEmail = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,10})+$/;
|
||||||
|
return !!patternEmail.test(email) || "Tento e-mail není platný";
|
||||||
|
};
|
||||||
|
export const minLength = (length) => (val) => val.length >= length || `Zadej alespoň ${length} znaky`
|
||||||
|
export const maxLength = (length) => (val) => val.length < length || `Zadej maximálně ${length} znaků`
|
||||||
|
|
||||||
|
|
||||||
|
export const isEmptyArray = () => (val) => {
|
||||||
|
return (val && val.length !== 0) || "Select item, please";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isNumber = () => (val) => {
|
||||||
|
return !isNaN(val) || "Only numbers are allowed";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasNoWhitespace = () => (val) => {
|
||||||
|
return /^\S*$/.test(val) || "Don't use whitespaces";
|
||||||
|
};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<q-icon name="person" v-else/>
|
<q-icon name="person" v-else/>
|
||||||
|
|
||||||
<q-menu auto-close>
|
<q-menu auto-close>
|
||||||
<q-list style="min-width: 100px">
|
<q-list>
|
||||||
<q-item clickable :to="{name: 'profile'}">
|
<q-item clickable :to="{name: 'profile'}">
|
||||||
<q-item-section>Osobní nastavení</q-item-section>
|
<q-item-section>Osobní nastavení</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
@@ -57,9 +57,11 @@
|
|||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
<q-page-container class="q-px-md">
|
<q-page-container>
|
||||||
<BreadcrumbsNavigation class="q-mx-md q-my-md" :items="breadcrumbs"/>
|
<BreadcrumbsNavigation class="q-mx-md q-my-md" :items="breadcrumbs"/>
|
||||||
|
<div class="q-mx-md q-my-md">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
|
</div>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -40,10 +40,14 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<AircraftEditSkeleton v-if="loading"/>
|
<AircraftEditSkeleton v-if="loading"/>
|
||||||
|
|
||||||
|
<q-card v-else class="q-mx-md">
|
||||||
|
<q-card-section>
|
||||||
<AircraftForm
|
<AircraftForm
|
||||||
v-else
|
|
||||||
@save="handleSave"
|
@save="handleSave"
|
||||||
:default-values="aircraft"
|
:default-values="aircraft"
|
||||||
:is-request-pending="isRequestPending"
|
:is-request-pending="isRequestPending"
|
||||||
/>
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import AircraftForm from "components/aircrafts/AircraftForm.vue";
|
|||||||
import {useAircraftsStore} from "stores/Aircrafts";
|
import {useAircraftsStore} from "stores/Aircrafts";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
import {onMounted, ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
|
import {successObject} from "src/composables/dialog";
|
||||||
|
|
||||||
let isRequestPending = ref(false);
|
let isRequestPending = ref(false);
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
@@ -12,24 +13,25 @@ const router = useRouter();
|
|||||||
const handleSave = (formData) => {
|
const handleSave = (formData) => {
|
||||||
isRequestPending.value = true;
|
isRequestPending.value = true;
|
||||||
useAircraftsStore().create(formData).then(() => {
|
useAircraftsStore().create(formData).then(() => {
|
||||||
$q.notify({message: "Letadlo úspěšně vytvořeno", position: "top", color: "positive"});
|
$q.notify(successObject("Letadlo úspěšně vytvořeno"));
|
||||||
|
|
||||||
router.push({name: "aircrafts"});
|
router.push({name: "aircrafts"});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
isRequestPending.value = true;
|
isRequestPending.value = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
useAppStore().setPageTitle("Nové letadlo");
|
useAppStore().setPageTitle("Nové letadlo");
|
||||||
useAppStore().setBreadcrumbs([
|
useAppStore().setBreadcrumbs([
|
||||||
{to: {name: "aircrafts"}, label: "Letadla"},
|
{to: {name: "aircrafts"}, label: "Letadla"},
|
||||||
{label: "Nové"}
|
{label: "Nové"}
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<q-card class="q-mx-md q-my-md">
|
||||||
|
<q-card-section>
|
||||||
<AircraftForm @save="handleSave" :is-request-pending="isRequestPending"/>
|
<AircraftForm @save="handleSave" :is-request-pending="isRequestPending"/>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted} from "vue";
|
import {onMounted} from "vue";
|
||||||
import AircraftsList from "components/aircrafts/AircraftsList.vue";
|
import AircraftsList from "components/aircrafts/AircraftsList.vue";
|
||||||
import AircraftsListSkeleton from "components/aircrafts/AircraftsListSkeleton.vue";
|
|
||||||
import {aircrafts, fetchAircrafts, loading} from "src/composables/objects/aircraft";
|
import {aircrafts, fetchAircrafts, loading} from "src/composables/objects/aircraft";
|
||||||
import FAB from "components/FAB.vue";
|
import FAB from "components/FAB.vue";
|
||||||
|
import BasicListSkeleton from "components/BasicListSkeleton.vue";
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchAircrafts();
|
fetchAircrafts();
|
||||||
@@ -11,9 +11,24 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AircraftsListSkeleton v-if="loading" />
|
<BasicListSkeleton v-if="loading"/>
|
||||||
|
|
||||||
<AircraftsList class="q-my-lg" :items="aircrafts" v-else />
|
<div class="q-pa-md q-gutter-sm" v-else-if="!loading && aircrafts.length === 0">
|
||||||
|
<q-banner class="bg-light-blue text-white">
|
||||||
|
<template v-slot:avatar>
|
||||||
|
<q-icon name="info"/>
|
||||||
|
</template>
|
||||||
|
Aktuálně nemáš přidané žádné letadlo.
|
||||||
|
<template v-slot:action>
|
||||||
|
<q-btn flat color="white" :to="{name: 'addAircraft'}" label="Vytvořit letadlo"/>
|
||||||
|
</template>
|
||||||
|
</q-banner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AircraftsList
|
||||||
|
v-else
|
||||||
|
:items="aircrafts"
|
||||||
|
/>
|
||||||
|
|
||||||
<FAB mode="add" :router-link="{name: 'addAircraft'}"/>
|
<FAB mode="add" :router-link="{name: 'addAircraft'}"/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted} from "vue";
|
import {onMounted} from "vue";
|
||||||
import CopilotListSkeleton from "components/copilots/CopilotListSkeleton.vue";
|
|
||||||
import {fetchCopilots, loading, copilots} from "src/composables/objects/copilot";
|
import {fetchCopilots, loading, copilots} from "src/composables/objects/copilot";
|
||||||
import CopilotsList from "components/copilots/CopilotsList.vue";
|
import CopilotsList from "components/copilots/CopilotsList.vue";
|
||||||
import FAB from "components/FAB.vue";
|
import FAB from "components/FAB.vue";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
|
import {aircrafts} from "src/composables/objects/aircraft";
|
||||||
|
import BasicListSkeleton from "components/BasicListSkeleton.vue";
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchCopilots();
|
fetchCopilots();
|
||||||
@@ -13,10 +14,21 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CopilotListSkeleton v-if="loading"/>
|
<BasicListSkeleton :items-count-min="5" :items-count-max="10" v-if="loading"/>
|
||||||
|
|
||||||
|
<div class="q-pa-md q-gutter-sm" v-else-if="!loading && copilots.length === 0">
|
||||||
|
<q-banner class="bg-light-blue text-white">
|
||||||
|
<template v-slot:avatar>
|
||||||
|
<q-icon name="info"/>
|
||||||
|
</template>
|
||||||
|
Zatím tu není nikdo. Přidej si let s někým nebo kopilota vytvoř ručně tu.
|
||||||
|
<template v-slot:action>
|
||||||
|
<q-btn flat color="white" :to="{name: 'addCopilot'}" label="Přidat"/>
|
||||||
|
</template>
|
||||||
|
</q-banner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CopilotsList :items="copilots" v-else/>
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<CopilotsList class="q-my-lg" :items="copilots"/>
|
|
||||||
<FAB :router-link="{name: 'addCopilot'}" mode="add"/>
|
<FAB :router-link="{name: 'addCopilot'}" mode="add"/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ onMounted(() => {
|
|||||||
</q-banner>
|
</q-banner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FlightsList v-else :items="flights" @delete="handleDeleteFlight($q, $event)" />
|
<FlightsList class="q-mx-md q-my-md" v-else :items="flights" @delete="handleDeleteFlight($q, $event)" />
|
||||||
|
|
||||||
<FAB mode="add" :router-link="{name: 'addFlight'}"/>
|
<FAB mode="add" :router-link="{name: 'addFlight'}"/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted} from "vue";
|
import {onMounted} from "vue";
|
||||||
import POIListSkeleton from "components/poi/POIListSkeleton.vue";
|
|
||||||
import {fetchPOIList, loading, pointsOfInterest} from "src/composables/objects/poi";
|
import {fetchPOIList, loading, pointsOfInterest} from "src/composables/objects/poi";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
import POIList from "components/poi/POIList.vue";
|
import POIList from "components/poi/POIList.vue";
|
||||||
import FAB from "components/FAB.vue";
|
import FAB from "components/FAB.vue";
|
||||||
|
import BasicListSkeleton from "components/BasicListSkeleton.vue";
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchPOIList();
|
fetchPOIList();
|
||||||
@@ -14,9 +14,25 @@ useAppStore().setPageTitle("Zajímavá místa")
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<POIListSkeleton v-if="loading"/>
|
<BasicListSkeleton :items-count-min="5" :items-count-max="10" v-if="loading"/>
|
||||||
|
|
||||||
<POIList v-else class="q-my-lg" :items="pointsOfInterest"/>
|
<div class="q-pa-md q-gutter-sm" v-else-if="!loading && pointsOfInterest.length === 0">
|
||||||
|
<q-banner class="bg-light-blue text-white">
|
||||||
|
<template v-slot:avatar>
|
||||||
|
<q-icon name="info"/>
|
||||||
|
</template>
|
||||||
|
Nemáš tu uložené žádné zajímavé místo. Přidej si let, nastav si tam ručně trasu a jednotlivé body se zobrazí
|
||||||
|
i v tomto seznamu.
|
||||||
|
<template v-slot:action>
|
||||||
|
<q-btn flat color="white" :to="{name: 'addPoi'}" label="Přidat"/>
|
||||||
|
</template>
|
||||||
|
</q-banner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<POIList
|
||||||
|
v-else
|
||||||
|
:items="pointsOfInterest"
|
||||||
|
/>
|
||||||
|
|
||||||
<FAB :router-link="{name: 'addPOI'}" mode="add"/>
|
<FAB :router-link="{name: 'addPOI'}" mode="add"/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,39 +1,48 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
import {ref} from "vue";
|
|
||||||
import {useUserStore} from "stores/User";
|
import {useUserStore} from "stores/User";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
|
import {errorObject, successObject} from "src/composables/dialog";
|
||||||
const email = ref("");
|
import {useAppStore} from "stores/App";
|
||||||
const name = ref("");
|
import {isRequired, isValidEmail, minLength} from "src/composables/form/rules";
|
||||||
const password = ref("");
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
const form = ref(null);
|
||||||
|
|
||||||
|
useAppStore().setPageTitle("Registrace");
|
||||||
|
|
||||||
const registerUser = () => {
|
const registerUser = () => {
|
||||||
useUserStore().register({
|
if (!form.value.validate()) return;
|
||||||
email: email.value,
|
|
||||||
name: name.value,
|
useUserStore().register(formData).then(() => {
|
||||||
password: password.value
|
useUserStore().login({email: formData.email, password: formData.password}).then(() => {
|
||||||
}).then(() => {
|
$q.notify(successObject("Registrace úspěšná, vítej!"));
|
||||||
$q.notify({message: "Registration Ok, now login", position: "top", color: "positive"});
|
router.push({name: "myFlights"});
|
||||||
router.push({name: 'login'})
|
})
|
||||||
|
|
||||||
}).catch(({response}) => {
|
}).catch(({response}) => {
|
||||||
$q.notify({
|
console.log(response.detail);
|
||||||
message: "Registration failed",
|
if (response.detail === 'User already exists') {
|
||||||
position: "top",
|
$q.notify(errorObject("Uživatel s tímto e-mailem je již registrovaný. Zkus se přihlásit."));
|
||||||
color: "negative",
|
} else {
|
||||||
timeout: 0,
|
$q.notify(errorObject("Registrace se nepovedla, zkus to prosím znovu za chvíli"));
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: "Dismiss", color: "white"
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
password: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
email: [isRequired, isValidEmail],
|
||||||
|
password: [isRequired, minLength(4)]
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -43,19 +52,23 @@ const registerUser = () => {
|
|||||||
<h4 class="text-h5 text-white q-my-md">Registrace</h4>
|
<h4 class="text-h5 text-white q-my-md">Registrace</h4>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-form class="q-px-sm q-pt-xl q-pb-lg" @submit.prevent="registerUser">
|
<q-form
|
||||||
<q-input square clearable v-model="name" type="text" label="Name">
|
ref="form"
|
||||||
|
class="q-px-sm q-pt-xl q-pb-lg"
|
||||||
|
@submit.prevent="registerUser"
|
||||||
|
>
|
||||||
|
<q-input square clearable v-model="formData.name" type="text" label="Jméno">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="person"/>
|
<q-icon name="person"/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-input square clearable v-model="email" type="email" label="E-mail">
|
<q-input square clearable v-model="formData.email" type="email" label="E-mail" :rules="rules.email">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="email"/>
|
<q-icon name="email"/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
<q-input square clearable v-model="password" type="password" label="Password">
|
<q-input square clearable v-model="formData.password" type="password" label="Heslo" :rules="rules.password">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="lock"/>
|
<q-icon name="lock"/>
|
||||||
</template>
|
</template>
|
||||||
@@ -63,7 +76,13 @@ const registerUser = () => {
|
|||||||
</q-form>
|
</q-form>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions class="q-px-lg">
|
<q-card-actions class="q-px-lg">
|
||||||
<q-btn @click="registerUser" unelevated size="lg" class="full-width" label="Registrovat"/>
|
<q-btn
|
||||||
|
@click="registerUser"
|
||||||
|
unelevated
|
||||||
|
size="lg"
|
||||||
|
class="full-width"
|
||||||
|
label="Registrovat"
|
||||||
|
/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
<q-card-section class="text-center q-pa-sm">
|
<q-card-section class="text-center q-pa-sm">
|
||||||
<router-link :to="{name: 'login'}" class="text-grey-6">Zpět na login</router-link>
|
<router-link :to="{name: 'login'}" class="text-grey-6">Zpět na login</router-link>
|
||||||
|
|||||||
Reference in New Issue
Block a user