Homepage, verejna cast, vetsi sada uprav a oprav
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
routeAdd: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
labelAdd: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Přidat"
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-banner class="bg-light-blue text-white">
|
||||
<template v-slot:avatar>
|
||||
<q-icon name="info"/>
|
||||
</template>
|
||||
<slot/>
|
||||
|
||||
<template v-slot:action v-if="routeAdd">
|
||||
<q-btn flat color="white" :to="routeAdd" :label="labelAdd"/>
|
||||
</template>
|
||||
</q-banner>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,7 +47,7 @@ const fullwidthBtn = computed(() => $q.screen.width <= $q.screen.sizes.md);
|
||||
color="primary"
|
||||
type="submit"
|
||||
size="lg"
|
||||
:class="{'q-my-lg': true, 'q-mx-lg': true, 'btn--fullwidth': fullwidthBtn}"
|
||||
:class="{'q-my-lg': true, 'q-mx-lg': !fullwidthBtn, 'btn--fullwidth': fullwidthBtn}"
|
||||
v-if="saveButton"
|
||||
:icon="props.saveButton.icon"
|
||||
:loading="props.isRequestPending"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
|
||||
import {randomInt} from "src/composables/random";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-skeleton type="QAvatar" size="30px"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-skeleton type="text" :width="randomInt(50, 100)+'px'"/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
@@ -1,10 +1,9 @@
|
||||
<script setup>
|
||||
|
||||
import {onMounted, reactive} from "vue";
|
||||
import FormWrapper from "components/FormWrapper.vue";
|
||||
import {setDefaultData} from "src/composables/form/formHandling";
|
||||
import {defaultValues, isRequestPending} from "src/composables/form/props";
|
||||
import {isRequired, maxLength, minLength} from "src/composables/form/rules";
|
||||
import {isNumber, isRequired, maxLength, minLength} from "src/composables/form/rules";
|
||||
import {commonProps} from "src/composables/inputs";
|
||||
|
||||
const formData = reactive({
|
||||
@@ -24,14 +23,8 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const rules = {
|
||||
callSign: [
|
||||
isRequired,
|
||||
minLength(4),
|
||||
maxLength(8)
|
||||
],
|
||||
seats: [
|
||||
isNumber
|
||||
]
|
||||
callSign: [isRequired, minLength(4), maxLength(8)],
|
||||
seats: [isRequired, isNumber]
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -41,7 +34,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormWrapper v-model="formData" :is-request-pending="isRequestPending">
|
||||
<FormWrapper v-model="formData" :is-request-pending="props.isRequestPending">
|
||||
<q-img :src="props.defaultValues.photoUrl" v-if="props.defaultValues.photoUrl"/>
|
||||
|
||||
<q-input
|
||||
@@ -67,8 +60,8 @@ onMounted(() => {
|
||||
|
||||
<q-input
|
||||
type="number"
|
||||
v-model="formData.seats"
|
||||
label="Počet míst"
|
||||
v-model.number="formData.seats"
|
||||
label="Počet míst (včetně pilota)"
|
||||
min="1"
|
||||
:rules="rules.seats"
|
||||
v-bind="commonProps"
|
||||
|
||||
@@ -7,6 +7,11 @@ const props = defineProps({
|
||||
flight: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showCopilots: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -70,16 +75,20 @@ const props = defineProps({
|
||||
{{ minutesToHumanReadable(flight.durationMinCalculated) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-if="flight.copilot">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="people"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
Link jen pro prihlasene!
|
||||
<router-link :to="{name: 'copilotDetail', params: {id: flight.copilot.id}}">
|
||||
{{ flight.copilot.name }}
|
||||
</router-link>
|
||||
</q-item-section>
|
||||
<q-item v-if="flight.copilots?.length > 0">
|
||||
<q-list>
|
||||
<q-item v-for="copilot in flight.copilots" :key="copilot.id">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="people"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<router-link :to="{name: 'copilotDetail', params: {id: copilot.id}}">
|
||||
{{ copilot.name }}
|
||||
</router-link>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-item>
|
||||
|
||||
</q-list>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
<script setup>
|
||||
import {randomInt} from "src/composables/random";
|
||||
import ListItemSkeleton from "components/ListItemSkeleton.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
skeleton
|
||||
<q-card class="flight-detail-card">
|
||||
<q-skeleton animation="fade" height="400px" square/>
|
||||
|
||||
<q-card-section>
|
||||
<q-list bordered separator>
|
||||
|
||||
<ListItemSkeleton/>
|
||||
<ListItemSkeleton/>
|
||||
|
||||
<q-item>
|
||||
<q-list>
|
||||
<ListItemSkeleton v-for="i in randomInt(2, 5)" :key="i"/>
|
||||
</q-list>
|
||||
</q-item>
|
||||
|
||||
<ListItemSkeleton/>
|
||||
<ListItemSkeleton/>
|
||||
|
||||
</q-list>
|
||||
|
||||
<article class="q-my-lg">
|
||||
<q-skeleton
|
||||
v-for="i in randomInt(5,8)" :key="i"
|
||||
type="text" :width="randomInt(90, 100)+'%'"
|
||||
/>
|
||||
</article>
|
||||
|
||||
<q-skeleton height="400px" square class="q-mt-lg"/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref, watch} from "vue";
|
||||
import FormWrapper from "components/FormWrapper.vue";
|
||||
import {setDefaultData} from "src/composables/form/formHandling";
|
||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||
import {commonProps} from "src/composables/inputs";
|
||||
|
||||
const props = defineProps({
|
||||
flight: {
|
||||
@@ -16,28 +16,27 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const formData = reactive({
|
||||
copilot: null,
|
||||
copilots: [],
|
||||
})
|
||||
|
||||
const solo = ref(true);
|
||||
const solo = ref(false);
|
||||
watch(() => solo.value, (newVal) => {
|
||||
if (!newVal) {
|
||||
formData.copilot = null;
|
||||
formData.copilots = [];
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.flight) {
|
||||
setDefaultData(formData, props.flight);
|
||||
solo.value = !formData.copilot;
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.flight, (newVal) => {
|
||||
if (newVal) {
|
||||
formData.copilots = newVal.copilots.map(copilot => ({id: copilot.id, name: copilot.name}));
|
||||
solo.value = formData.copilots.length === 0;
|
||||
}
|
||||
}, {deep: true, immediate: true});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormWrapper v-model="formData">
|
||||
|
||||
<q-btn-toggle
|
||||
spread
|
||||
no-caps
|
||||
@@ -48,14 +47,13 @@ onMounted(() => {
|
||||
|
||||
<div v-if="!solo">
|
||||
<ComboboxInput
|
||||
class="q-my-lg"
|
||||
outlined
|
||||
standout
|
||||
v-for="i in flight.aircraft.seats - 1"
|
||||
:key="i"
|
||||
v-bind="commonProps"
|
||||
:options="copilots"
|
||||
v-model="formData.copilot"
|
||||
:key="formData.copilot"
|
||||
v-model="formData.copilots[i-1]"
|
||||
:clearable="true"
|
||||
label="Druhy pilot"
|
||||
:label="i === 1 ? 'Kopilot' : 'Pasažér'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ const filterFn = (val, update, abort) => {
|
||||
:v-bind="$attrs"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item v-if="inputValue">
|
||||
<q-item-section avatar>
|
||||
<q-avatar text-color="green" size="32px" icon="add" />
|
||||
</q-item-section>
|
||||
|
||||
@@ -27,6 +27,7 @@ watch(
|
||||
map.fitBounds(bounds);
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -64,47 +64,48 @@ const showExtra = ref(false);
|
||||
@keyup.right="nextPhoto"
|
||||
>
|
||||
<q-card-section>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="text-center">
|
||||
<inner-image-zoom move-type="pan" :zoomScale="0.75" :hide-hint="true" :src="photo.url"/>
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
class="absolute all-pointer-events"
|
||||
round
|
||||
flat
|
||||
icon="info"
|
||||
:text-color="showExtra ? 'primary' : 'white'"
|
||||
style="background: rgba(0, 0, 0, 0.25); top: 8px; right: 8px"
|
||||
@click="showExtra = !showExtra"
|
||||
/>
|
||||
<q-btn
|
||||
class="absolute all-pointer-events"
|
||||
style="background: rgba(0, 0, 0, 0.25); top: 50%; left: 8px;"
|
||||
@click="prevPhoto"
|
||||
v-if="photoIndex > 0"
|
||||
icon="chevron_left"
|
||||
text-color="white"
|
||||
flat
|
||||
rounded
|
||||
>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
class="absolute all-pointer-events"
|
||||
style="background: rgba(0, 0, 0, 0.25); top: 50%; right: 8px;"
|
||||
@click="nextPhoto"
|
||||
v-if="photoIndex < photos.length - 1"
|
||||
icon-right="chevron_right"
|
||||
text-color="white"
|
||||
flat
|
||||
rounded
|
||||
>
|
||||
</q-btn>
|
||||
<strong v-if="photo.name !== ''">{{ photo.name }}</strong>
|
||||
<p v-if="photo.description !== ''">{{ photo.description }}</p>
|
||||
<div class="relative-position">
|
||||
<div class="text-center">
|
||||
<inner-image-zoom move-type="pan" :zoomScale="0.75" :hide-hint="true" :src="photo.url"/>
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
class="absolute all-pointer-events"
|
||||
round
|
||||
flat
|
||||
icon="info"
|
||||
:text-color="showExtra ? 'primary' : 'white'"
|
||||
style="background: rgba(0, 0, 0, 0.25); top: 8px; right: 8px"
|
||||
@click="showExtra = !showExtra"
|
||||
/>
|
||||
<q-btn
|
||||
class="absolute all-pointer-events"
|
||||
style="background: rgba(0, 0, 0, 0.25); top: 50%; left: 8px;"
|
||||
@click="prevPhoto"
|
||||
v-if="photoIndex > 0"
|
||||
icon="chevron_left"
|
||||
text-color="white"
|
||||
flat
|
||||
rounded
|
||||
>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
class="absolute all-pointer-events"
|
||||
style="background: rgba(0, 0, 0, 0.25); top: 50%; right: 8px;"
|
||||
@click="nextPhoto"
|
||||
v-if="photoIndex < photos.length - 1"
|
||||
icon-right="chevron_right"
|
||||
text-color="white"
|
||||
flat
|
||||
rounded
|
||||
>
|
||||
</q-btn>
|
||||
<strong v-if="photo.name !== ''">{{ photo.name }}</strong>
|
||||
<p v-if="photo.description !== ''">{{ photo.description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12 q-my-md" v-if="showExtra">
|
||||
<EmbeddedMap
|
||||
v-if="photo.gpsLatitude && photo.gpsLongitude"
|
||||
@@ -150,7 +151,6 @@ const showExtra = ref(false);
|
||||
<style>
|
||||
.iiz {
|
||||
width: 100%;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.iiz__img {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {computed, ref} from "vue";
|
||||
import {setDefaultData} from "src/composables/form/formHandling";
|
||||
import DialogWrapper from "components/DialogWrapper.vue";
|
||||
import FormWrapper from "components/FormWrapper.vue";
|
||||
import {commonProps} from "src/composables/inputs";
|
||||
|
||||
const props = defineProps({
|
||||
photo: {
|
||||
@@ -32,8 +33,10 @@ const setup = (() => {
|
||||
|
||||
<q-card-section>
|
||||
<FormWrapper :save-button="false">
|
||||
<q-input v-model="formData.name" label="Název"/>
|
||||
<q-input type="textarea" v-model="formData.description" label="Popis"/>
|
||||
<q-input v-model="formData.name" label="Název" v-bind="commonProps"/>
|
||||
<q-input type="textarea" v-model="formData.description" label="Popis" v-bind="commonProps"/>
|
||||
|
||||
{{ locations }}
|
||||
|
||||
<q-select
|
||||
:options="locations"
|
||||
@@ -41,11 +44,12 @@ const setup = (() => {
|
||||
option-value="id"
|
||||
emit-value
|
||||
map-options
|
||||
v-bind="commonProps"
|
||||
v-model="formData.pointOfInterestId"
|
||||
label="POI"
|
||||
/>
|
||||
|
||||
<q-checkbox v-model="formData.isFlightCover" label="Úvodní fotografie k letu?" />
|
||||
<q-checkbox v-model="formData.isFlightCover" label="Úvodní fotografie k letu?"/>
|
||||
</FormWrapper>
|
||||
</q-card-section>
|
||||
</DialogWrapper>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
import {user as pilot} from "src/composables/objects/user";
|
||||
import {FORMAT_DATE, formatDatetime} from "src/composables/datetimeUtils";
|
||||
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-card flat style="margin: 0 auto; max-width: 400px">
|
||||
<q-img :src="pilot.titleImageUrl" v-if="pilot.titleImageUrl"/>
|
||||
<q-card-section>
|
||||
<div class="text-center" :style="`margin-top: ${pilot.titleImageUrl ? '-100' : '0'}px`">
|
||||
<AvatarFromPhoto :size="150" :src="pilot.avatarImageUrl" v-if="pilot.avatarImageUrl" class="q-mb-sm"/>
|
||||
<div class="text-h3">{{ pilot.name }}</div>
|
||||
<div class="text-subtitle1 text-weight-thin">
|
||||
Registrován: {{ formatDatetime(pilot.createdAt, FORMAT_DATE) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="q-my-lg" v-html="pilot.description"/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
@@ -4,6 +4,7 @@ import {commonProps} from "src/composables/inputs";
|
||||
import {reactive, watch} from "vue";
|
||||
import {setDefaultData} from "src/composables/form/formHandling";
|
||||
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
|
||||
import {user as pilot} from "src/composables/objects/user";
|
||||
|
||||
const props = defineProps({
|
||||
user: {
|
||||
@@ -27,7 +28,7 @@ watch(() => props.user, (newVal) => {
|
||||
<FormWrapper v-model="formData">
|
||||
|
||||
<div class="text-center">
|
||||
<AvatarFromPhoto :size="200" :src="user.avatarImageUrl" />
|
||||
<AvatarFromPhoto :size="200" :src="user.avatarImageUrl" v-if="user.avatarImageUrl" />
|
||||
</div>
|
||||
|
||||
<q-file
|
||||
|
||||
@@ -26,19 +26,21 @@ watch(() => props.user, (newVal) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-list>
|
||||
<q-item v-if="user.publicUsername">
|
||||
<router-link target="_blank" :to="{name: 'publicProfileDetail', params: {username: user.publicUsername}}">
|
||||
@{{user.publicUsername}}
|
||||
</router-link>
|
||||
</q-item>
|
||||
<q-item v-else>
|
||||
<q-icon name="" color="red" /> Nastav si veřejné uživatelské jméno pro zpřístupnění profilu a sdílení záznamů s ostatními
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<FormWrapper v-model="formData">
|
||||
<q-input v-model="formData.name" label="Jméno" v-bind="commonProps"/>
|
||||
|
||||
<q-list>
|
||||
<q-item v-if="user.publicUsername">
|
||||
<router-link target="_blank" :to="{name: 'publicProfileDetail', params: {username: user.publicUsername}}">
|
||||
@{{ user.publicUsername }}
|
||||
</router-link>
|
||||
</q-item>
|
||||
<q-item v-else>
|
||||
<q-icon name="" color="red"/>
|
||||
Nastav si veřejné uživatelské jméno pro zpřístupnění profilu a sdílení záznamů s ostatními
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<q-input v-model="formData.name" label="Jméno" v-bind="commonProps"/>
|
||||
<q-input
|
||||
v-model="formData.publicUsername"
|
||||
label="Veřejné uživatelské jméno"
|
||||
@@ -46,6 +48,6 @@ watch(() => props.user, (newVal) => {
|
||||
v-bind="commonProps"
|
||||
/>
|
||||
|
||||
<RichTextEditor v-model="formData.description" label="Veřejný podpis" />
|
||||
<RichTextEditor v-model="formData.description" label="Veřejný podpis"/>
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<script setup> </script>
|
||||
|
||||
<template>Skeleton</template>
|
||||
@@ -4,7 +4,7 @@ export const isValidEmail = (email) => {
|
||||
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 maxLength = (length) => (val) => val.length <= length || `Zadej maximálně ${length} znaků`
|
||||
|
||||
|
||||
export const isEmptyArray = () => (val) => {
|
||||
|
||||
@@ -28,9 +28,9 @@ export const fetchFlight = (flightId, username) => {
|
||||
|
||||
export const flights = storeToRefs(useFlightsStore()).items;
|
||||
|
||||
export const fetchFlights = () => {
|
||||
export const fetchFlights = (username = null) => {
|
||||
loading.value = true;
|
||||
useFlightsStore().fetch().finally(() => {
|
||||
useFlightsStore().fetch(username).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import {ref} from "vue";
|
||||
import {useUserStore} from "stores/User";
|
||||
|
||||
export const loading = ref(true);
|
||||
export const user = ref(null);
|
||||
export const fetchUser = (username) => {
|
||||
loading.value = true;
|
||||
return useUserStore().fetchDetail(username)
|
||||
.then(respUser => {
|
||||
user.value = respUser;
|
||||
return respUser;
|
||||
})
|
||||
.finally(() => loading.value = false);
|
||||
};
|
||||
+19
-18
@@ -12,25 +12,26 @@ const uploadLink = createUploadLink({
|
||||
})
|
||||
|
||||
const handleRefreshToken = (forward, operation) => {
|
||||
return fromPromise(
|
||||
useUserStore().refreshAccessToken()
|
||||
.catch((error) => {
|
||||
window.location = "/login";
|
||||
})
|
||||
)
|
||||
.filter((value) => Boolean(value))
|
||||
.flatMap((accessToken) => {
|
||||
const oldHeaders = operation.getContext().headers;
|
||||
operation.setContext({
|
||||
headers: {
|
||||
...oldHeaders,
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
return fromPromise(
|
||||
useUserStore().refreshAccessToken()
|
||||
.catch((error) => {
|
||||
console.log("neumim refresh");
|
||||
// window.location = "/login";
|
||||
})
|
||||
)
|
||||
.filter((value) => Boolean(value))
|
||||
.flatMap((accessToken) => {
|
||||
const oldHeaders = operation.getContext().headers;
|
||||
operation.setContext({
|
||||
headers: {
|
||||
...oldHeaders,
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
// retry the request, returning the new observable
|
||||
return forward(operation);
|
||||
});
|
||||
// retry the request, returning the new observable
|
||||
return forward(operation);
|
||||
});
|
||||
}
|
||||
|
||||
const errorLink = onError(
|
||||
|
||||
@@ -8,6 +8,7 @@ fragment aircraftFullInfo on Aircraft {
|
||||
description
|
||||
manufacturer
|
||||
photoUrl
|
||||
seats
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ fragment flightFullInfo on Flight {
|
||||
...airportInfo
|
||||
}
|
||||
|
||||
copilot {
|
||||
copilots {
|
||||
id
|
||||
name
|
||||
}
|
||||
@@ -91,11 +91,6 @@ fragment flightListInfo on Flight {
|
||||
thumbnailUrl
|
||||
}
|
||||
|
||||
copilot {
|
||||
id
|
||||
name
|
||||
}
|
||||
|
||||
takeoffAirport {
|
||||
...airportInfo
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import gql from "graphql-tag";
|
||||
import {FRAGMENT_FLIGHT_FULL_INFO, FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight";
|
||||
|
||||
export const FLIGHTS_LIST = gql`
|
||||
query FlightsList {
|
||||
flights {
|
||||
query FlightsList($username: String) {
|
||||
flights(username: $username) {
|
||||
...flightListInfo
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ ${FRAGMENT_FLIGHT_LIST_INFO}
|
||||
`;
|
||||
|
||||
export const FLIGHT_DETAIL = gql`
|
||||
query FlightDetail($id: Int!) {
|
||||
flight(id: $id) {
|
||||
query FlightDetail($id: Int!, $username: String) {
|
||||
flight(id: $id, username: $username) {
|
||||
...flightFullInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<q-layout view="lHh Lpr lFf">
|
||||
<q-page-container>
|
||||
<router-view :loggedUser="loggedUser" class="q-py-md"/>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
<router-view :loggedUser="loggedUser" class="q-py-md"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<q-header>
|
||||
<q-toolbar>
|
||||
<q-toolbar-title>
|
||||
{{ pageTitle ? `${pageTitle} | Polétání.cz` : "Polétání.cz" }}
|
||||
{{ pageTitle }}
|
||||
</q-toolbar-title>
|
||||
|
||||
<template v-if="loggedUser.id">
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<q-page-container>
|
||||
<BreadcrumbsNavigation class="q-mx-md q-my-md" :items="breadcrumbs"/>
|
||||
<router-view :loggedUser="loggedUser" class="q-my-lg"/>
|
||||
<router-view class="q-my-lg q-mx-lg"/>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
@@ -47,8 +47,9 @@ useMeta(() => ({
|
||||
}));
|
||||
|
||||
onMounted(() => {
|
||||
if (useUserStore().getAccessToken()) {
|
||||
useUserStore().fetchLoggedUserDetail();
|
||||
if (loggedUser || useUserStore().hasAccessToken()) {
|
||||
// poll once per 5 minutes to keep access token active
|
||||
setInterval(useUserStore().fetchLoggedUserDetail, 5*60*1000);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const openedTab = ref("profile");
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="publicProfile">
|
||||
<PublicProfileInfo :user="loggedUser" />
|
||||
<PublicProfileInfo @save="handleSave" :user="loggedUser" />
|
||||
</q-tab-panel>
|
||||
|
||||
</q-tab-panels>
|
||||
|
||||
+3
-13
@@ -42,14 +42,14 @@ watch(aircraft, (newVal) => {
|
||||
<template v-else>
|
||||
|
||||
<q-card class="q-mx-md q-my-md flight-detail-card">
|
||||
<q-img :src="aircraft.photoUrl"/>
|
||||
<q-img :src="aircraft.photoUrl" v-if="aircraft.photoUrl" />
|
||||
|
||||
<q-card-section>
|
||||
<q-list>
|
||||
<q-item>
|
||||
<q-item v-if="aircraft.manufacturer">
|
||||
Výrobce: {{ aircraft.manufacturer }}
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item v-if="aircraft.model">
|
||||
Model: {{ aircraft.model }}
|
||||
</q-item>
|
||||
</q-list>
|
||||
@@ -66,16 +66,6 @@ watch(aircraft, (newVal) => {
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
|
||||
|
||||
<!-- <h2>Limits</h2>-->
|
||||
<!-- Wind: {{ aircraft.limits.wind.frontKt }}/{{ aircraft.limits.wind.sideKt }} kt-->
|
||||
<!-- Neprekrocitelna rychlost: {{ aircraft.limits.speed.vmax }}-->
|
||||
<!-- Cestovni rychlost:-->
|
||||
<!-- Maximalni rychlost s klapkami:-->
|
||||
<!-- Padova rychlost bez klapek:-->
|
||||
<!-- Padova rychlost s klapkami (plne vysunute):-->
|
||||
|
||||
|
||||
<FAB :router-link='{name: "aircraftEdit", params: {id: aircraft.id}}' mode="edit"/>
|
||||
</template>
|
||||
|
||||
+3
-2
@@ -11,7 +11,8 @@ import {useAppStore} from "stores/App";
|
||||
import {copilot} from "src/composables/objects/copilot";
|
||||
|
||||
|
||||
let isRequestPending = ref(false);
|
||||
const isRequestPending = ref(false);
|
||||
const $router = useRouter();
|
||||
const $q = useQuasar();
|
||||
const aircraftId = parseInt(useRoute().params.id);
|
||||
|
||||
@@ -19,7 +20,7 @@ const handleSave = (formData) => {
|
||||
isRequestPending.value = true;
|
||||
useAircraftsStore().update(aircraftId, formData).then(() => {
|
||||
$q.notify(successObject("Letadlo úspěšně upraveno"));
|
||||
useRouter().push({name: "aircrafts"});
|
||||
$router.push({name: "aircrafts"});
|
||||
}).finally(() => {
|
||||
isRequestPending.value = false;
|
||||
})
|
||||
+2
-1
@@ -7,9 +7,10 @@ import {ref} from "vue";
|
||||
import {useAppStore} from "stores/App";
|
||||
import {successObject} from "src/composables/dialog";
|
||||
|
||||
let isRequestPending = ref(false);
|
||||
const $q = useQuasar();
|
||||
const router = useRouter();
|
||||
|
||||
let isRequestPending = ref(false);
|
||||
const handleSave = (formData) => {
|
||||
isRequestPending.value = true;
|
||||
useAircraftsStore().create(formData).then(() => {
|
||||
+4
-12
@@ -4,8 +4,8 @@ import {fetchCopilots, loading, copilots} from "src/composables/objects/copilot"
|
||||
import CopilotsList from "components/copilots/CopilotsList.vue";
|
||||
import FAB from "components/FAB.vue";
|
||||
import {useAppStore} from "stores/App";
|
||||
import {aircrafts} from "src/composables/objects/aircraft";
|
||||
import BasicListSkeleton from "components/BasicListSkeleton.vue";
|
||||
import BasicInfoBanner from "components/BasicInfoBanner.vue";
|
||||
|
||||
onMounted(() => {
|
||||
fetchCopilots();
|
||||
@@ -16,17 +16,9 @@ onMounted(() => {
|
||||
<template>
|
||||
<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>
|
||||
<BasicInfoBanner v-else-if="!loading && copilots.length === 0" :router-add="{name: 'addCopilot'}">
|
||||
Zatím tu není nikdo. Přidej si let s někým nebo kopilota vytvoř ručně tu.
|
||||
</BasicInfoBanner>
|
||||
|
||||
<CopilotsList :items="copilots" v-else/>
|
||||
|
||||
+6
-8
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import {useRouter} from "vue-router";
|
||||
import {useQuasar} from "quasar";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {ref} from "vue";
|
||||
import {successObject} from "src/composables/dialog";
|
||||
import {useCopilotsStore} from "stores/Copilots";
|
||||
import CopilotForm from "components/copilots/CopilotForm.vue";
|
||||
@@ -20,13 +20,11 @@ const handleSave = (formData) => {
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
useAppStore().setPageTitle("Nový spolucestující");
|
||||
useAppStore().setBreadcrumbs([
|
||||
{to: {name: "copilots"}, label: "Spolucestující"},
|
||||
{label: "Nový"}
|
||||
]);
|
||||
});
|
||||
useAppStore().setPageTitle("Nový spolucestující");
|
||||
useAppStore().setBreadcrumbs([
|
||||
{to: {name: "copilots"}, label: "Spolucestující"},
|
||||
{label: "Nový"}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
+9
-5
@@ -33,8 +33,6 @@ const locations = storeToRefs(usePointsOfInterestStore()).items;
|
||||
const airports = storeToRefs(useAirportsStore()).items;
|
||||
const copilots = storeToRefs(useCopilotsStore()).items;
|
||||
|
||||
const disabledSteps = computed(() => flight.value.solo ? ["copilot"] : [flight.value.solo]);
|
||||
|
||||
const formStep = ref("");
|
||||
watch(() => route.params.step, (val) => formStep.value = val);
|
||||
|
||||
@@ -52,7 +50,8 @@ onMounted(() => {
|
||||
const handleFlightEdit = (data) => {
|
||||
useFlightsStore().update(flightId, data).then(() => {
|
||||
$q.notify(successObject("Úspěšně upraveno"));
|
||||
fetchFlight(flightId);
|
||||
fetchFlight(flightId); // je tu potreba ten refetch? Ve storu se upravuje, mozna by stacilo jen getterem upravit
|
||||
// hodnotu `flight`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,7 +105,7 @@ const openPhotoEdit = (photo) => {
|
||||
<q-breadcrumbs-el>Editace</q-breadcrumbs-el>
|
||||
</q-breadcrumbs>
|
||||
|
||||
<FlightFormSteps :router-edit-mode="true" :flightId="flightId" :disabled="disabledSteps"/>
|
||||
<FlightFormSteps :router-edit-mode="true" :flightId="flightId" />
|
||||
|
||||
<q-tab-panels v-model="formStep">
|
||||
<q-tab-panel name="basic">
|
||||
@@ -138,7 +137,12 @@ const openPhotoEdit = (photo) => {
|
||||
@save="handlePhotosUpload"
|
||||
/>
|
||||
|
||||
<PhotoForm v-model="openEdit" :locations="locations" :photo="editPhoto" @save="handlePhotoUpdate"/>
|
||||
<PhotoForm
|
||||
v-model="openEdit"
|
||||
:locations="locations"
|
||||
:photo="editPhoto"
|
||||
@save="handlePhotoUpdate"
|
||||
/>
|
||||
|
||||
<PhotosList
|
||||
:photos="flight.photos"
|
||||
+33
-37
@@ -12,7 +12,6 @@ import PhotoCarousel from "components/flights/PhotoCarousel.vue";
|
||||
import FlightTrackMap from "components/flights/FlightTrackMap.vue";
|
||||
import {useUserStore} from "stores/User";
|
||||
|
||||
|
||||
const $q = useQuasar();
|
||||
const $router = useRouter();
|
||||
const flightId = parseInt(useRoute().params.id);
|
||||
@@ -59,7 +58,6 @@ const copyLinkToClipboard = () => {
|
||||
copyToClipboard(window.location.origin + url.href).then(() => {
|
||||
$q.notify(successObject("Odkaz zkopírován"))
|
||||
});
|
||||
;
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -68,45 +66,43 @@ const copyLinkToClipboard = () => {
|
||||
<template>
|
||||
<FlightDetailSkeleton v-if="loading || !flight"/>
|
||||
|
||||
<template v-else>
|
||||
<q-card class="q-mx-md q-my-md flight-detail-card">
|
||||
<PhotoCarousel :cover-photo-url="flight.coverPhoto?.url" :photos="flight.photos"/>
|
||||
<q-card class="flight-detail-card" v-else>
|
||||
<PhotoCarousel :cover-photo-url="flight.coverPhoto?.url" :photos="flight.photos"/>
|
||||
|
||||
<q-card-actions class="q-my-md">
|
||||
<div class="col-grow">
|
||||
<q-btn
|
||||
flat
|
||||
:to="{name: 'flightEdit', params:{id: flight.id}}"
|
||||
icon="edit"
|
||||
text-color="primary"
|
||||
label="Upravit"
|
||||
/>
|
||||
<q-card-actions class="q-my-md">
|
||||
<div class="col-grow">
|
||||
<q-btn
|
||||
flat
|
||||
:to="{name: 'flightEdit', params:{id: flight.id}}"
|
||||
icon="edit"
|
||||
text-color="primary"
|
||||
label="Upravit"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
@click="togglePublicVisibility"
|
||||
:icon="flight.isPublic ? 'visibility_off' : 'visibility'"
|
||||
flat
|
||||
:label="flight.isPublic ? 'Skrýt' : 'Zveřejnit'"
|
||||
/>
|
||||
<q-btn
|
||||
@click="togglePublicVisibility"
|
||||
:icon="flight.isPublic ? 'visibility_off' : 'visibility'"
|
||||
flat
|
||||
:label="flight.isPublic ? 'Skrýt' : 'Zveřejnit'"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
icon="link"
|
||||
@click="copyLinkToClipboard()"
|
||||
flat
|
||||
v-if="flight.isPublic"
|
||||
label="Získat odkaz"
|
||||
/>
|
||||
</div>
|
||||
<q-btn @click="handleDeleteAndRedirect" icon="delete" flat text-color="red" label="Smazat"/>
|
||||
</q-card-actions>
|
||||
<q-btn
|
||||
icon="link"
|
||||
@click="copyLinkToClipboard()"
|
||||
flat
|
||||
v-if="flight.isPublic"
|
||||
label="Získat odkaz"
|
||||
/>
|
||||
</div>
|
||||
<q-btn @click="handleDeleteAndRedirect" icon="delete" flat text-color="red" label="Smazat"/>
|
||||
</q-card-actions>
|
||||
|
||||
<q-card-section>
|
||||
<FlightBasicInfo :flight="flight"/>
|
||||
<q-card-section>
|
||||
<FlightBasicInfo :flight="flight" :show-copilots="true"/>
|
||||
|
||||
<article class="q-my-lg" v-html="flight.description"/>
|
||||
<article class="q-my-lg" v-html="flight.description"/>
|
||||
|
||||
<FlightTrackMap :flight="flight"/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
<FlightTrackMap :flight="flight"/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
@@ -7,17 +7,21 @@ import FlightTrackMap from "components/flights/FlightTrackMap.vue";
|
||||
import FlightBasicInfo from "components/flights/FlightBasicInfo.vue";
|
||||
import {useAppStore} from "stores/App";
|
||||
import FlightDetailSkeleton from "components/flights/FlightDetailSkeleton.vue";
|
||||
import {fetchUser, loading as pilotLoading, user as pilot} from "src/composables/objects/user";
|
||||
import PublicProfileSkeleton from "components/profile/PublicProfileSkeleton.vue";
|
||||
import PilotProfileCard from "components/profile/PilotProfileCard.vue";
|
||||
|
||||
const routeParams = useRoute().params
|
||||
|
||||
const routeParams = useRoute().params;
|
||||
onMounted(() => {
|
||||
fetchFlight(routeParams.id, routeParams.username);
|
||||
fetchUser(routeParams.username)
|
||||
})
|
||||
|
||||
watch(flight, (newVal) => {
|
||||
const flightNameFormatted = flightName(newVal);
|
||||
useAppStore().setPageTitle(flightNameFormatted);
|
||||
useAppStore().setBreadcrumbs([
|
||||
{to: {name: 'homepage'}, label: "Polétání.cz"},
|
||||
{to: {name: "publicProfileDetail", params: {username: routeParams.username}}, label: `@${routeParams.username}`},
|
||||
{label: flightNameFormatted}
|
||||
]);
|
||||
@@ -26,7 +30,6 @@ watch(flight, (newVal) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<FlightDetailSkeleton v-if="loading || !flight"/>
|
||||
|
||||
@@ -35,21 +38,22 @@ watch(flight, (newVal) => {
|
||||
Pozor! Tento let není publikován a nebude tak dostupný pro ostatní!
|
||||
</q-banner>
|
||||
|
||||
|
||||
<q-card class="q-mx-md q-my-md flight-detail-card">
|
||||
<PhotoCarousel
|
||||
v-if="flight.coverPhoto"
|
||||
:cover-photo-url="flight.coverPhoto.url"
|
||||
:cover-photo-url="flight.coverPhoto?.url"
|
||||
:photos="flight.photos"
|
||||
/>
|
||||
<q-card-section>
|
||||
<FlightBasicInfo :flight="flight"/>
|
||||
<FlightBasicInfo :flight="flight" :show-copilots="false"/>
|
||||
|
||||
<article class="q-my-lg" v-html="flight.description"/>
|
||||
|
||||
<FlightTrackMap :flight="flight"/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<PublicProfileSkeleton v-if="pilotLoading"/>
|
||||
<PilotProfileCard :pilot="pilot" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,88 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Pochlub se svým polétáním!</h1>
|
||||
<q-layout view="lHh Lpr lFf" style="background: url('hp/bg.jpg'); background-size: cover;">
|
||||
|
||||
<p>Létáš? Kam si archivuješ fotky a trasy? Polétání.cz je aplikace pro sdílení informací o tvých výletech vzduchem.</p>
|
||||
</div>
|
||||
<q-header class="bg-semi-transparent text-black" elevated>
|
||||
<q-toolbar>
|
||||
<q-toolbar-title>
|
||||
Polétání.cz
|
||||
</q-toolbar-title>
|
||||
|
||||
<q-btn rounded class="q-mx-sm" :to="{name: 'login'}" flat>Přihlášení</q-btn>
|
||||
<q-btn rounded class="q-mx-sm" :to="{name: 'registration'}" color="primary">Registrace</q-btn>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
<q-page-container class="q-py-none">
|
||||
<q-page>
|
||||
<div class="row q-mx-md">
|
||||
<div class="xs-hide sm-hide col-md-6 text-center">
|
||||
<q-img src="hp/app_screenshot.png" fit="contain" class="app-screen" alt="Nahled aplikace"/>
|
||||
</div>
|
||||
|
||||
<div class="col col-md-6">
|
||||
<div class="info-box bg-semi-transparent q-mx-lg q-py-lg q-px-lg rounded-borders">
|
||||
<div class="text-h2 text-center">Pochlub se polétáním</div>
|
||||
|
||||
<q-timeline color="primary" class="q-my-lg">
|
||||
<q-timeline-entry title="Zaznamenej si let">
|
||||
<div>
|
||||
Zapiš si odkud, kdy a čím jsi letěl. Aplikace sama stáhne informace o počasí při vzletu a přistání.
|
||||
</div>
|
||||
</q-timeline-entry>
|
||||
|
||||
|
||||
<q-timeline-entry title="Vyber a nahraj fotky">
|
||||
<div>
|
||||
Máš nějaké fotky z letu? Nahraj je ke svému letu! A pokud mají v sobě uloženou informaci o poloze, polétání.cz ti zobrazí i místo kde fotka vznikla.
|
||||
</div>
|
||||
</q-timeline-entry>
|
||||
|
||||
<q-timeline-entry title="Ulož trasu">
|
||||
<div>
|
||||
Polétání.cz umí pracovat s trasou ve formátu GPX. Pokud ale tento záznam nemáš, můžeš si naklikat trasu ručně.
|
||||
</div>
|
||||
</q-timeline-entry>
|
||||
|
||||
<q-timeline-entry title="Popiš zážitek">
|
||||
<div>
|
||||
Určitě se po ceste stalo něco vzrušujícího. Proč o tom nenapsat pár vět?
|
||||
</div>
|
||||
</q-timeline-entry>
|
||||
|
||||
<q-timeline-entry title="Sdílej">
|
||||
<div>
|
||||
Létat v sóle je super, ale ještě lepší je se o zážitek podělit. Jedním kliknutím můžeš svůj let zveřejnit a sdílet odkaz kde chceš. Třeba s tvým kopilotem.
|
||||
</div>
|
||||
</q-timeline-entry>
|
||||
</q-timeline>
|
||||
|
||||
<div class="text-center q-my-lg">
|
||||
<q-btn size="xl" :to="{name: 'registration'}" rounded color="secondary">Založit profil</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.bg-semi-transparent {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.info-box {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.app-screen {
|
||||
max-height: 85vh;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -15,33 +15,38 @@ const logUser = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="window-height window-width row justify-center">
|
||||
<q-card square style="width:95%; max-width: 450px; height:400px;" class="q-mt-lg">
|
||||
<q-card-section class="bg-primary">
|
||||
<h4 class="text-h5 q-my-md text-white">Login</h4>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form class="q-px-sm q-pt-xl" @submit.prevent="logUser()">
|
||||
<q-input square clearable v-model="email" type="email" label="Email">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="email"/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input square clearable v-model="password" type="password" label="Password">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="lock"/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
<q-layout view="lHh Lpr lFf">
|
||||
<q-page-container>
|
||||
|
||||
<q-card-actions class="q-px-lg">
|
||||
<q-btn unelevated size="lg" class="full-width" label="Přihlásit se" @click.prevent="logUser()"/>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-center q-pa-sm">
|
||||
<router-link :to="{name: 'forgottenPassword'}" class="text-grey-6">Zapomenuté heslo</router-link>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-page class="window-height window-width row justify-center">
|
||||
<q-card square style="width:95%; max-width: 450px; height:400px;" class="q-mt-lg">
|
||||
<q-card-section class="bg-primary">
|
||||
<h4 class="text-h5 q-my-md text-white">Login</h4>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form class="q-px-sm q-pt-xl" @submit.prevent="logUser()">
|
||||
<q-input square clearable v-model="email" type="email" label="Email">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="email"/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input square clearable v-model="password" type="password" label="Password">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="lock"/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
|
||||
</q-page>
|
||||
<q-card-actions class="q-px-lg">
|
||||
<q-btn unelevated size="lg" class="full-width" label="Přihlásit se" @click.prevent="logUser()"/>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-center q-pa-sm">
|
||||
<router-link :to="{name: 'forgottenPassword'}" class="text-grey-6">Zapomenuté heslo</router-link>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
@@ -1,48 +1,38 @@
|
||||
<script setup>
|
||||
import {FORMAT_DATE, formatDatetime} from "src/composables/datetimeUtils";
|
||||
import {useAppStore} from "stores/App";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {useUserStore} from "stores/User";
|
||||
import {onMounted} from "vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
import {storeToRefs} from "pinia";
|
||||
import FlightsListSkeleton from "components/flights/FlightsListSkeleton.vue";
|
||||
import FlightsList from "components/flights/FlightsList.vue";
|
||||
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
|
||||
import {fetchFlights, flights, loading as flightsLoading} from "src/composables/objects/flight";
|
||||
import {fetchUser, user as pilot, loading as pilotLoading} from "src/composables/objects/user";
|
||||
import PublicProfileSkeleton from "components/profile/PublicProfileSkeleton.vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useUserStore} from "stores/User";
|
||||
import {useAppStore} from "stores/App";
|
||||
import PilotProfileCard from "components/profile/PilotProfileCard.vue";
|
||||
|
||||
const props = defineProps({
|
||||
loggedUser: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
})
|
||||
|
||||
const loggedUser = storeToRefs(useUserStore()).user;
|
||||
const username = useRoute().params.username;
|
||||
const pilot = ref(null)
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
useUserStore().fetchDetail(username).then((user) => {
|
||||
pilot.value = user;
|
||||
useAppStore().setPageTitle(pilot.value.name);
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
});
|
||||
|
||||
useFlightsStore().fetch(username);
|
||||
fetchUser(username).then(user => {
|
||||
useAppStore().setPageTitle(user.publicUsername);
|
||||
useAppStore().setBreadcrumbs([
|
||||
{to: {name: 'homepage'}, label: "Polétání.cz"},
|
||||
{label: `@${user.publicUsername}`},
|
||||
])
|
||||
})
|
||||
fetchFlights(username);
|
||||
})
|
||||
|
||||
const flights = storeToRefs(useFlightsStore()).items;
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FlightsListSkeleton v-if="loading || !pilot"/>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8">
|
||||
<FlightsListSkeleton v-if="flightsLoading"/>
|
||||
|
||||
<div class="row" v-else>
|
||||
<div class="col">
|
||||
<q-banner class="bg-primary text-white" v-if="flights.length === 0">
|
||||
<q-banner class="bg-primary text-white" v-else-if="!flightsLoading && flights.length === 0">
|
||||
Tento pilot zatím nepublikoval žádný let.
|
||||
</q-banner>
|
||||
|
||||
@@ -54,23 +44,10 @@ const flights = storeToRefs(useFlightsStore()).items;
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="col-4 sm-hide">
|
||||
<q-card class="q-mr-md" flat>
|
||||
<q-img :src="pilot.titleImageUrl" v-if="pilot.titleImageUrl"/>
|
||||
<q-card-section>
|
||||
<div class="text-center" :style="`margin-top: ${pilot.titleImageUrl ? '-100' : '0'}px`">
|
||||
<AvatarFromPhoto :size="150" :src="pilot.avatarImageUrl" v-if="pilot.avatarImageUrl" class="q-mb-sm"/>
|
||||
<div class="text-h3">{{ pilot.name }}</div>
|
||||
<div class="text-subtitle1 text-weight-thin">
|
||||
Registrován: {{ formatDatetime(pilot.createdAt, FORMAT_DATE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<PublicProfileSkeleton v-if="pilotLoading"/>
|
||||
|
||||
|
||||
|
||||
<div class="q-my-lg" v-html="pilot.description"/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<PilotProfileCard v-else :pilot="pilot" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -46,47 +46,52 @@ const rules = {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="row justify-center">
|
||||
<q-card style="width:95%; max-width: 450px; height: 500px">
|
||||
<q-card-section class="bg-primary">
|
||||
<h4 class="text-h5 text-white q-my-md">Registrace</h4>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form
|
||||
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>
|
||||
<q-icon name="person"/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-layout view="lHh Lpr lFf">
|
||||
<q-page-container>
|
||||
|
||||
<q-input square clearable v-model="formData.email" type="email" label="E-mail" :rules="rules.email">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="email"/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input square clearable v-model="formData.password" type="password" label="Heslo" :rules="rules.password">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="lock"/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
<q-card-actions class="q-px-lg">
|
||||
<q-btn
|
||||
@click="registerUser"
|
||||
unelevated
|
||||
size="lg"
|
||||
class="full-width"
|
||||
label="Registrovat"
|
||||
/>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-center q-pa-sm">
|
||||
<router-link :to="{name: 'login'}" class="text-grey-6">Zpět na login</router-link>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-page>
|
||||
<q-page class="row justify-center">
|
||||
<q-card style="width:95%; max-width: 450px; height: 500px">
|
||||
<q-card-section class="bg-primary">
|
||||
<h4 class="text-h5 text-white q-my-md">Registrace</h4>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form
|
||||
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>
|
||||
<q-icon name="person"/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-input square clearable v-model="formData.email" type="email" label="E-mail" :rules="rules.email">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="email"/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input square clearable v-model="formData.password" type="password" label="Heslo" :rules="rules.password">
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="lock"/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
<q-card-actions class="q-px-lg">
|
||||
<q-btn
|
||||
@click="registerUser"
|
||||
unelevated
|
||||
size="lg"
|
||||
class="full-width"
|
||||
label="Registrovat"
|
||||
/>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-center q-pa-sm">
|
||||
<router-link :to="{name: 'login'}" class="text-grey-6">Zpět na login</router-link>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
+19
-17
@@ -4,11 +4,13 @@ const routes = [
|
||||
{path: '/login', name: "login", component: () => import('pages/public/LoginPage.vue'), meta: {layout: PlainLayout}},
|
||||
{path: '/register', name: "registration", component: () => import('pages/public/RegisterPage.vue'), meta: {layout: PlainLayout}},
|
||||
{path: '/forgotten-password', name: "forgottenPassword", component: () => import('pages/public/RegisterPage.vue'), meta: {layout: PlainLayout}},
|
||||
|
||||
{path: '', name: "homepage", component: () => import('pages/public/IndexPage.vue'), meta: {layout: PlainLayout}},
|
||||
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('layouts/PublicLayout.vue'),
|
||||
children: [
|
||||
{path: '', name: "homepage", component: () => import('pages/public/IndexPage.vue')},
|
||||
{path: 'about', name: "about", component: () => import('pages/public/AboutPage.vue')},
|
||||
{path: ':username', name: "publicProfileDetail", component: () => import('pages/public/PilotProfilePage.vue')},
|
||||
{path: ':username/:id', name: "publicFlightDetail", component: () => import('pages/public/FlightDetailPage.vue')},
|
||||
@@ -22,22 +24,22 @@ const routes = [
|
||||
},
|
||||
children: [
|
||||
{path: '', name: "profile", component: () => import('pages/profile/ProfilePage.vue')},
|
||||
{path: 'my-flights', name: "myFlights", component: () => import('pages/profile/lists/FlightsPage.vue')},
|
||||
{path: 'my-flights/add', name: "addFlight", component: () => import('pages/profile/forms/NewFlightPage.vue')},
|
||||
{path: 'my-flights/:id', name: "flightDetail", component: () => import('pages/profile/details/FlightDetailPage.vue')},
|
||||
{path: 'my-flights/:id/edit/:step?', name: "flightEdit", component: () => import('pages/profile/forms/EditFlightPage.vue')},
|
||||
{path: 'poi', name: "poi", component: () => import('pages/profile/lists/PointsOfInterestPage.vue')},
|
||||
{path: 'poi/add', name: "addPOI", component: () => import('pages/profile/forms/NewPointOfInterestPage.vue')},
|
||||
{path: 'poi/:id', name: "poiDetail", component: () => import('pages/profile/details/PointOfInterestDetailPage.vue')},
|
||||
{path: 'poi/:id/edit', name: "poiEdit", component: () => import('pages/profile/forms/EditPointOfInterestPage.vue')},
|
||||
{path: 'copilots', name: "copilots", component: () => import('pages/profile/lists/CopilotsPage.vue')},
|
||||
{path: 'copilots/add', name: "addCopilot", component: () => import('pages/profile/forms/NewCopilotPage.vue')},
|
||||
{path: 'copilots/:id', name: "copilotDetail", component: () => import('pages/profile/details/CopilotDetailPage.vue')},
|
||||
{path: 'copilots/:id/edit', name: "copilotEdit", component: () => import('pages/profile/forms/EditCopilotPage.vue')},
|
||||
{path: 'aircrafts', name: "aircrafts", component: () => import('pages/profile/lists/AircraftsPage.vue')},
|
||||
{path: 'aircrafts/new', name: "addAircraft", component: () => import('pages/profile/forms/NewAircraftsPage.vue')},
|
||||
{path: 'aircrafts/:id', name: "aircraftDetail", component: () => import('pages/profile/details/AircraftDetailPage.vue')},
|
||||
{path: 'aircrafts/:id/edit', name: "aircraftEdit", component: () => import('pages/profile/forms/EditAircraftPage.vue')},
|
||||
{path: 'my-flights', name: "myFlights", component: () => import('pages/profile/flight/FlightsPage.vue')},
|
||||
{path: 'my-flights/add', name: "addFlight", component: () => import('pages/profile/flight/NewFlightPage.vue')},
|
||||
{path: 'my-flights/:id', name: "flightDetail", component: () => import('pages/profile/flight/FlightDetailPage.vue')},
|
||||
{path: 'my-flights/:id/edit/:step?', name: "flightEdit", component: () => import('pages/profile/flight/EditFlightPage.vue')},
|
||||
{path: 'poi', name: "poi", component: () => import('pages/profile/poi/PointsOfInterestPage.vue')},
|
||||
{path: 'poi/add', name: "addPOI", component: () => import('pages/profile/poi/NewPointOfInterestPage.vue')},
|
||||
{path: 'poi/:id', name: "poiDetail", component: () => import('pages/profile/poi/PointOfInterestDetailPage.vue')},
|
||||
{path: 'poi/:id/edit', name: "poiEdit", component: () => import('pages/profile/poi/EditPointOfInterestPage.vue')},
|
||||
{path: 'copilots', name: "copilots", component: () => import('pages/profile/copilot/CopilotsPage.vue')},
|
||||
{path: 'copilots/add', name: "addCopilot", component: () => import('pages/profile/copilot/NewCopilotPage.vue')},
|
||||
{path: 'copilots/:id', name: "copilotDetail", component: () => import('pages/profile/copilot/CopilotDetailPage.vue')},
|
||||
{path: 'copilots/:id/edit', name: "copilotEdit", component: () => import('pages/profile/copilot/EditCopilotPage.vue')},
|
||||
{path: 'aircrafts', name: "aircrafts", component: () => import('pages/profile/aircraft/AircraftsPage.vue')},
|
||||
{path: 'aircrafts/new', name: "addAircraft", component: () => import('pages/profile/aircraft/NewAircraftsPage.vue')},
|
||||
{path: 'aircrafts/:id', name: "aircraftDetail", component: () => import('pages/profile/aircraft/AircraftDetailPage.vue')},
|
||||
{path: 'aircrafts/:id/edit', name: "aircraftEdit", component: () => import('pages/profile/aircraft/EditAircraftPage.vue')},
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export const useAircraftsStore = defineStore('Aircrafts', {
|
||||
|
||||
actions: {
|
||||
fetchDetail(id) {
|
||||
console.log(id);
|
||||
return apolloWrapper
|
||||
.query(AIRCRAFT_DETAIL, {id})
|
||||
.then((result) => result.data.aircraft);
|
||||
|
||||
@@ -36,20 +36,23 @@ export const useFlightsStore = defineStore('Flights', {
|
||||
return flight;
|
||||
});
|
||||
},
|
||||
fetchDetail(id, username) {
|
||||
fetchDetail(id, username = null) {
|
||||
if (typeof id !== "number") {
|
||||
id = parseInt(id);
|
||||
}
|
||||
return apolloWrapper.query(FLIGHT_DETAIL, {id, username}).then(result => result.data.flight);
|
||||
},
|
||||
fetch(username) {
|
||||
fetch(username = null) {
|
||||
return apolloWrapper
|
||||
.query(FLIGHTS_LIST, {username})
|
||||
.then(({data}) => {
|
||||
.then((resp) => {
|
||||
console.log(resp);
|
||||
const data = resp.data;
|
||||
|
||||
this.items = data.flights;
|
||||
|
||||
return data.flights;
|
||||
});
|
||||
}).catch(err => console.log(err));
|
||||
},
|
||||
delete(id) {
|
||||
return apolloWrapper.mutate(DELETE_FLIGHT, {id}).then(({data}) => {
|
||||
|
||||
@@ -72,6 +72,10 @@ export const useUserStore = defineStore('LoggedUser', {
|
||||
return localStorage.getItem(`accessToken`);
|
||||
},
|
||||
|
||||
hasAccessToken() {
|
||||
return !!localStorage.getItem("accessToken");
|
||||
},
|
||||
|
||||
async refreshAccessToken() {
|
||||
return api.post("/refresh")
|
||||
.then(({data}) => {
|
||||
|
||||
Reference in New Issue
Block a user