Vylepseni SSR, pridani meta informaci, zatim dost WIP

This commit is contained in:
Michal Kváček
2023-11-28 11:35:53 +01:00
parent 86605ad53e
commit 11cc6ad107
28 changed files with 371 additions and 403 deletions
+17 -15
View File
@@ -2,26 +2,28 @@
FROM node:20-alpine as develop-stage FROM node:20-alpine as develop-stage
WORKDIR /app WORKDIR /app
COPY package*.json ./ RUN yarn global add @quasar/cli
RUN yarn global add @quasar/cli && yarn --production=false
COPY package.json yarn.lock ./
RUN yarn --production=false
RUN npx update-browserslist-db@latest
COPY . . COPY . .
# build stage # build stage
FROM develop-stage as build-stage FROM develop-stage as build-stage
#ARG API_URL
#ENV VUE_API_URL $API_URL
RUN yarn --production=false
RUN quasar build -m ssr RUN quasar build -m ssr
# production stage FROM node:20-alpine as production-stage
#FROM nginx:1.25-alpine as production-stage
#COPY conf/nginx.conf /etc/nginx/nginx.conf
#COPY --from=build-stage /app/dist/spa /app/html
#EXPOSE 80
#
#CMD ["nginx", "-g", "daemon off;"]
FROM keymetrics/pm2:latest-alpine as production-stage ENV NODE_ENV production
EXPOSE 3000
WORKDIR /app
COPY process.yml /app
COPY --from=build-stage /app/dict/ssr/package.json /app
RUN yarn global add pm2 && yarn && yarn cache clean
COPY --from=build-stage /app/dist/ssr /app
CMD ["pm2-runtime", "/app/process.yml"]
+7 -2
View File
@@ -1,7 +1,12 @@
version: "3.3" version: "3.4"
services: services:
poletani-app: poletani-app:
image: "docker.kvacek.cz/poletani/app:latest" image: "docker.kvacek.cz/poletani/app:latest"
# build:
# context: .
# dockerfile: Dockerfile
# target: "production-stage"
restart: always restart: always
ports: ports:
- 9000:80 # - 9000:80
- 9000:3000
+1
View File
@@ -24,6 +24,7 @@ services:
## command: ls -l /app ## command: ls -l /app
app: app:
network_mode: host
build: build:
context: . context: .
target: "develop-stage" target: "develop-stage"
-3
View File
@@ -1,10 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title><%= productName %></title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no"> <meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>"> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
+5
View File
@@ -0,0 +1,5 @@
apps:
- name: poletani
script: /app/index.js
exec_mode: cluster
instances: 3
+3 -3
View File
@@ -23,7 +23,7 @@ module.exports = configure(function (ctx) {
}, },
// https://v2.quasar.dev/quasar-cli/prefetch-feature // https://v2.quasar.dev/quasar-cli/prefetch-feature
// preFetch: true, preFetch: true,
// app boot file (/poletani/boot) // app boot file (/poletani/boot)
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
@@ -56,7 +56,7 @@ module.exports = configure(function (ctx) {
build: { build: {
target: { target: {
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'], browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node18' node: 'node20'
}, },
env: { env: {
@@ -76,7 +76,7 @@ module.exports = configure(function (ctx) {
// env: {}, // env: {},
// rawDefine: {} // rawDefine: {}
// ignorePublicFolder: true, // ignorePublicFolder: true,
// minify: false, minify: true,
// polyfillModulePreload: true, // polyfillModulePreload: true,
// distDir // distDir
-1
View File
@@ -7,7 +7,6 @@ import {useAppStore} from "stores/App";
const route = useRoute(); const route = useRoute();
const layoutComponent = computed(() => { const layoutComponent = computed(() => {
if (!route.meta) return null; if (!route.meta) return null;
return route.meta.layout; return route.meta.layout;
+5 -9
View File
@@ -1,4 +1,3 @@
// import {boot} from "quasar/wrappers";
import {ApolloClient, ApolloLink, fromPromise, InMemoryCache} from '@apollo/client/core' import {ApolloClient, ApolloLink, fromPromise, InMemoryCache} from '@apollo/client/core'
import {createUploadLink} from 'apollo-upload-client'; import {createUploadLink} from 'apollo-upload-client';
import {onError} from "@apollo/client/link/error"; import {onError} from "@apollo/client/link/error";
@@ -8,6 +7,8 @@ import {Notify} from "quasar";
import {errorObject, warningObject} from "src/composables/dialog"; import {errorObject, warningObject} from "src/composables/dialog";
const handleRefreshToken = (store, forward, operation) => { const handleRefreshToken = (store, forward, operation) => {
if (!operation._refreshUnauthorizedUser) return;
return fromPromise(useUserStore(store).refreshAccessToken() return fromPromise(useUserStore(store).refreshAccessToken()
.catch((error) => { .catch((error) => {
Notify.create(warningObject("Tvoje přihlášení vypřešlo. Přihlaš se prosím znovu.")); Notify.create(warningObject("Tvoje přihlášení vypřešlo. Přihlaš se prosím znovu."));
@@ -18,7 +19,8 @@ const handleRefreshToken = (store, forward, operation) => {
const oldHeaders = operation.getContext().headers; const oldHeaders = operation.getContext().headers;
operation.setContext({ operation.setContext({
headers: { headers: {
...oldHeaders, authorization: `Bearer ${accessToken}`, ...oldHeaders,
authorization: `Bearer ${accessToken}`,
}, },
}); });
@@ -27,8 +29,6 @@ const handleRefreshToken = (store, forward, operation) => {
}); });
} }
export const apolloWrapper = {}; export const apolloWrapper = {};
export default (({store}) => { export default (({store}) => {
const uploadLink = createUploadLink({ const uploadLink = createUploadLink({
@@ -64,14 +64,9 @@ export default (({store}) => {
} }
}); });
const apolloClient = new ApolloClient({ const apolloClient = new ApolloClient({
link: ApolloLink.from([authLink, errorLink, uploadLink]), cache: new InMemoryCache(), connectToDevTools: true, link: ApolloLink.from([authLink, errorLink, uploadLink]), cache: new InMemoryCache(), connectToDevTools: true,
}); });
// const apolloProvider = createApolloProvider({
// defaultClient: apolloClient,
// });
apolloWrapper.query = (query, variables = {}) => { apolloWrapper.query = (query, variables = {}) => {
return apolloClient.query({ return apolloClient.query({
@@ -79,6 +74,7 @@ export default (({store}) => {
}) })
.then((response) => response) .then((response) => response)
.catch(err => { .catch(err => {
if (!err.toString().includes("401") && !err.toString().includes("Not authorized"))
console.error("Error during query: ", err) console.error("Error during query: ", err)
return err; return err;
}) })
+22
View File
@@ -0,0 +1,22 @@
export const metaData = ({title, description, keywords, image}) => {
return {
title: title || "",
titleTemplate(title) {
return title !== '' ? `${title} - Polétání.cz` : 'Polétání.cz'
},
meta: {
description: {name: "description", content: description || ""},
keywords: {name: "keywords", content: (keywords || []).join(", ")},
ogImage: image ? {property: "og:image", content: image} : undefined,
ogTitle: {
property: "og:title",
content: title,
template(ogTitle) {
return (!ogTitle || ogTitle === '') ? "Polétání.cz" : `${ogTitle} - Polétání.cz`
}
}
}
}
}
export const commonKeywords = ["zápisník", "létání", "ultralight", "letectví", "fotogalerie", "výlet", "vyhlídkový let", "navigační let"];
-1
View File
@@ -13,7 +13,6 @@ export const croppedArea = ref(null);
export const selectedAspectRatio = ref(null); export const selectedAspectRatio = ref(null);
watch(() => selectedAspectRatio.value, (newVal) => { watch(() => selectedAspectRatio.value, (newVal) => {
console.log(newVal);
cropper.setAspectRatio(newVal); cropper.setAspectRatio(newVal);
}) })
+1 -1
View File
@@ -2,7 +2,7 @@ import {ref} from "vue";
export const compare = ref(false); export const compare = ref(false);
export const splitterWidth = ref(750); // TODO: doresit onresize a zmenu velikosti export const splitterWidth = ref(750);
export const splitter = ref(50); export const splitter = ref(50);
export const onResize = (info) => { export const onResize = (info) => {
splitterWidth.value = info.width < 750 ? info.width : 750; splitterWidth.value = info.width < 750 ? info.width : 750;
+4 -10
View File
@@ -25,7 +25,6 @@
show-if-above show-if-above
bordered bordered
> >
<q-img class="absolute-top" v-if="!isQueryRunning && user" :src="user.titleImageUrl" style="height: 150px"/> <q-img class="absolute-top" v-if="!isQueryRunning && user" :src="user.titleImageUrl" style="height: 150px"/>
<q-spinner v-else/> <q-spinner v-else/>
@@ -44,14 +43,12 @@
</template> </template>
<script setup> <script setup>
import {onBeforeMount, onMounted, ref} from 'vue' import {onMounted, ref} from 'vue'
import NavigationButton from "components/navigation/NavigationButton.vue";
import {useUserStore} from "stores/User"; import {useUserStore} from "stores/User";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import BreadcrumbsNavigation from "components/navigation/BreadcrumbsNavigation.vue"; import BreadcrumbsNavigation from "components/navigation/BreadcrumbsNavigation.vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import {useMeta, useQuasar} from "quasar"; import {useQuasar} from "quasar";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import {successObject} from "src/composables/dialog"; import {successObject} from "src/composables/dialog";
import SidebarNavigation from "components/navigation/SidebarNavigation.vue"; import SidebarNavigation from "components/navigation/SidebarNavigation.vue";
@@ -61,17 +58,14 @@ const $q = useQuasar();
const $router = useRouter(); const $router = useRouter();
const {pageTitle, breadcrumbs} = storeToRefs(useAppStore()); const {pageTitle, breadcrumbs} = storeToRefs(useAppStore());
useMeta(() => ({
title: pageTitle.value !== '' ? `${pageTitle.value} - Polétání.cz` : 'Polétání.cz'
}));
const leftDrawerOpen = ref(false) const leftDrawerOpen = ref(false)
const toggleLeftDrawer = () => { const toggleLeftDrawer = () => {
leftDrawerOpen.value = !leftDrawerOpen.value leftDrawerOpen.value = !leftDrawerOpen.value
}; };
onBeforeMount(() => { onMounted(() => {
useUserStore().fetchLoggedUserDetail(); useUserStore().fetchLoggedUserDetail({refresh: true});
}) })
const {isQueryRunning, user} = storeToRefs(useUserStore()); const {isQueryRunning, user} = storeToRefs(useUserStore());
+3 -9
View File
@@ -3,7 +3,7 @@
<q-header> <q-header>
<q-toolbar> <q-toolbar>
<q-toolbar-title> <q-toolbar-title>
{{ pageTitle }} {{ pageTitle || "Polétání.cz" }}
</q-toolbar-title> </q-toolbar-title>
<template v-if="loggedUser && loggedUser.id"> <template v-if="loggedUser && loggedUser.id">
@@ -35,24 +35,18 @@ import {storeToRefs} from "pinia";
import {useUserStore} from "stores/User"; import {useUserStore} from "stores/User";
import {onMounted} from "vue"; import {onMounted} from "vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import {useMeta} from "quasar";
import BreadcrumbsNavigation from "components/navigation/BreadcrumbsNavigation.vue"; import BreadcrumbsNavigation from "components/navigation/BreadcrumbsNavigation.vue";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue"; import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
import {useRoute} from "vue-router";
const {pageTitle, breadcrumbs} = storeToRefs(useAppStore()); const {pageTitle, breadcrumbs} = storeToRefs(useAppStore());
const loggedUser = storeToRefs(useUserStore()).user; const loggedUser = storeToRefs(useUserStore()).user;
useMeta(() => ({
title: pageTitle.value !== '' ? `${pageTitle.value} - Polétání.cz` : 'Polétání.cz'
}));
onMounted(() => { onMounted(() => {
useUserStore().fetchLoggedUserDetail(); useUserStore().fetchLoggedUserDetail({refresh: false});
if (loggedUser || useUserStore().hasAccessToken()) { if (loggedUser || useUserStore().hasAccessToken()) {
// poll once per 5 minutes to keep access token active // poll once per 5 minutes to keep access token active
setInterval(useUserStore().fetchLoggedUserDetail, 5*60*1000); setInterval(() => useUserStore().fetchLoggedUserDetail({refresh: false}), 5 * 60 * 1000);
} }
}) })
+6 -4
View File
@@ -8,14 +8,16 @@ import {useAppStore} from "stores/App";
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue"; import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
import {useFlightsStore} from "stores/Flights"; import {useFlightsStore} from "stores/Flights";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import {useMeta} from "quasar";
import {metaData} from "src/composables/meta";
const $q = useQuasar(); const $q = useQuasar();
useAppStore().setPageTitle("Moje lety") useMeta(() => metaData({
onMounted(() => { title: "Moje lety"
useFlightsStore().fetch(); }));
})
useFlightsStore().fetch();
const {flights, isQueryRunning} = storeToRefs(useFlightsStore()); const {flights, isQueryRunning} = storeToRefs(useFlightsStore());
</script> </script>
+11 -4
View File
@@ -1,8 +1,15 @@
<script setup> <script setup>
</script> import {useMeta} from "quasar";
import {commonKeywords, metaData} from "src/composables/meta";
useMeta(() => metaData({
title: "",
description: "Polétání.cz je aplikace na ukládání a sdílení fotografií a tras z vyhlídkových letů.",
keywords: [...commonKeywords]
}));
</script>
<template> <template>
<q-layout view="lHh Lpr lFf" style="background: url('hp/bg.jpg'); background-size: cover;"> <q-layout view="lHh Lpr lFf" data-style="background: url('hp/bg.jpg'); background-size: cover;">
<q-header class="bg-semi-transparent text-black" elevated> <q-header class="bg-semi-transparent text-black" elevated>
<q-toolbar> <q-toolbar>
@@ -24,7 +31,7 @@
<div class="col col-md-6"> <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="info-box bg-semi-transparent q-mx-lg q-py-lg q-px-lg rounded-borders">
<div class="text-h3 text-center">Pochlub se polétáním</div> <div class="text-h3 text-center">Fotogalerie ze vzduchu</div>
<q-timeline color="primary" class="q-my-lg"> <q-timeline color="primary" class="q-my-lg">
<q-timeline-entry title="Zaznamenej si let"> <q-timeline-entry title="Zaznamenej si let">
@@ -60,7 +67,7 @@
</q-timeline> </q-timeline>
<div class="text-center q-my-lg"> <div class="text-center q-my-lg">
<q-btn size="xl" :to="{name: 'registration'}" rounded color="secondary">Založit profil</q-btn> <q-btn size="xl" :to="{name: 'registration'}" rounded color="secondary">Založit účet</q-btn>
</div> </div>
</div> </div>
</div> </div>
+14 -14
View File
@@ -1,28 +1,28 @@
<script setup> <script>
import {useRoute} from "vue-router";
import PublicProfileSkeleton from "components/profile/PublicProfileSkeleton.vue";
import {storeToRefs} from "pinia";
import {useUserStore} from "stores/User"; import {useUserStore} from "stores/User";
import PilotProfileCard from "components/profile/PilotProfileCard.vue";
import {onMounted} from "vue";
const loggedUser = storeToRefs(useUserStore()).user; export default {
const username = useRoute().params.username; preFetch({currentRoute}) {
return useUserStore().fetchPublicUserDetail(currentRoute.params.username);
},
}
</script>
<script setup>
import {storeToRefs} from "pinia";
import PilotProfileCard from "components/profile/PilotProfileCard.vue";
import PublicProfileSkeleton from "components/profile/PublicProfileSkeleton.vue";
const {publicUser: pilot, isQueryRunning: pilotLoading} = storeToRefs(useUserStore()); const {publicUser: pilot, isQueryRunning: pilotLoading} = storeToRefs(useUserStore());
onMounted(() => {
useUserStore().fetchPublicUserDetail(username);
})
</script> </script>
<template> <template>
<div class="row"> <div class="row">
<div class="col-12 col-md-8"> <div class="col-12 col-md-8">
<router-view :logged-user="loggedUser" :username="username"/> <router-view :pilot="pilot"/>
</div> </div>
<div class="col-12 col-md-4"> <div class="col-12 col-md-4">
<PublicProfileSkeleton v-if="pilotLoading || !pilot"/> <PublicProfileSkeleton v-if="pilotLoading && !pilot"/>
<PilotProfileCard v-else :pilot="pilot"/> <PilotProfileCard v-else :pilot="pilot"/>
</div> </div>
</div> </div>
-15
View File
@@ -1,15 +0,0 @@
<script setup>
import {useAircraftsStore} from "stores/Aircrafts";
import {onMounted} from "vue";
import {storeToRefs} from "pinia";
const {aircraft, isQueryRunning, aircrafts} = storeToRefs(useAircraftsStore());
onMounted(() => {
useAircraftsStore().fetch();
});
</script>
<template>
<q-spinner v-if="isQueryRunning" />
letadlo: {{ aircrafts }}
</template>
@@ -1,55 +1,45 @@
<script>
import {useCopilotsStore} from "stores/Copilots";
export default {
preFetch({currentRoute}) {
return useCopilotsStore().fetchDetail(currentRoute.params.id, currentRoute.params.username);
}
}
</script>
<script setup> <script setup>
import {useRoute} from "vue-router";
import {onMounted} from "vue";
import FlightsList from "components/flights/FlightsList.vue"; import FlightsList from "components/flights/FlightsList.vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue"; import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
import {useUserStore} from "stores/User";
import {useCopilotsStore} from "stores/Copilots";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
const $route = useRoute();
const copilotId = parseInt($route.params.id);
const props = defineProps({ const props = defineProps({
loggedUser: { pilot: {
type: Object, type: Object,
required: false required: false
}, },
username: {
type: String,
required: true
}
}); });
const {copilot, isQueryRunning} = storeToRefs(useCopilotsStore()); const {copilot, isQueryRunning} = storeToRefs(useCopilotsStore());
const {publicUser, isQueryRunning: isPilotQueryRunning} = storeToRefs(useUserStore());
onMounted(() => { useAppStore().setPageTitle(`${pilot.name} + ${copilot.value.name}`);
Promise.all([
useUserStore().fetchPublicUserDetail(props.username),
useCopilotsStore().fetchDetail(copilotId, props.username)
]).then(() => {
useAppStore().setPageTitle(`${publicUser.value.name} + ${copilot.value.name}`);
useAppStore().setBreadcrumbs([ useAppStore().setBreadcrumbs([
{to: {name: 'homepage'}, label: "Polétání.cz"}, {to: {name: 'homepage'}, label: "Polétání.cz"},
{to: {name: "publicProfileDetail", params: {username: props.username}}, label: `@${props.username}`}, {to: {name: "publicProfileDetail", params: {username: props.username}}, label: `@${props.username}`},
{label: publicUser.value.name} {label: props.pilot.name}
]); ]);
});
});
</script> </script>
<template> <template>
<BasicListSkeleton v-if="isQueryRunning || isPilotQueryRunning || !copilot" class="q-mb-md"/> <BasicListSkeleton v-if="isQueryRunning || !copilot" class="q-mb-md"/>
<template v-else> <template v-else>
<h3>Společné lety</h3> <h3>Společné lety</h3>
<FlightsList <FlightsList
:items="copilot.flights" :items="copilot.flights"
:route-detail="{name: 'publicFlightDetail', params: {username: username}}" :route-detail="{name: 'publicFlightDetail', params: {username: pilot.publicUsername}}"
class="q-mb-md" class="q-mb-md"
/> />
</template> </template>
@@ -1,19 +1,23 @@
<script>
import {useEventsStore} from "stores/Events";
export default {
preFetch({currentRoute}) {
return useEventsStore().fetchDetail(currentRoute.params.id, currentRoute.params.username);
}
}
</script>
<script setup> <script setup>
import {onMounted, watch} from "vue"; import {watch} from "vue";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import EventDetailSkeleton from "components/event/EventDetailSkeleton.vue"; import EventDetailSkeleton from "components/event/EventDetailSkeleton.vue";
import {FORMAT_DATE, formatDatetime} from "src/composables/datetimeUtils"; import {FORMAT_DATE, formatDatetime} from "src/composables/datetimeUtils";
import FlightsList from "components/flights/FlightsList.vue"; import FlightsList from "components/flights/FlightsList.vue";
import {useEventsStore} from "stores/Events";
import {useUserStore} from "stores/User";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
const routeParams = useRoute().params; const routeParams = useRoute().params;
onMounted(() => {
useEventsStore().fetchDetail(routeParams.id, routeParams.username);
useUserStore().fetchPublicUserDetail(routeParams.username);
})
const {isQueryRunning, event} = storeToRefs(useEventsStore()); const {isQueryRunning, event} = storeToRefs(useEventsStore());
@@ -26,7 +30,6 @@ watch(() => event.value, (newVal) => {
{label: newVal.name} {label: newVal.name}
]); ]);
}); });
</script> </script>
<template> <template>
@@ -1,43 +1,39 @@
<script setup> <script setup>
import FlightsListSkeleton from "components/flights/FlightsListSkeleton.vue"; import FlightsListSkeleton from "components/flights/FlightsListSkeleton.vue";
import EventsList from "components/event/EventsList.vue"; import EventsList from "components/event/EventsList.vue";
import {onMounted, watch} from "vue";
import PublicFlightEventSwitcher from "components/navigation/PublicFlightEventSwitcher.vue"; import PublicFlightEventSwitcher from "components/navigation/PublicFlightEventSwitcher.vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import {useEventsStore} from "stores/Events"; import {useEventsStore} from "stores/Events";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import {useMeta} from "quasar";
import {commonKeywords, metaData} from "src/composables/meta";
const props = defineProps({ const props = defineProps({
loggedUser: { pilot: {
type: Object, type: Object,
required: false
},
username: {
type: String,
required: true required: true
} }
}); });
watch(() => props.username, () => { useMeta(() => metaData({
}, {immediate: true}); title: `${props.pilot.publicUsername} - Události`,
keywords: [...commonKeywords, "pilot"],
description: "Detail pilota na poletani.cz",
}));
onMounted(() => { useAppStore().setPageTitle(`${props.pilot.publicUsername}`);
// const eventNameFormatted = eventName(newVal);
useAppStore().setPageTitle(`${props.username}`);
useAppStore().setBreadcrumbs([ useAppStore().setBreadcrumbs([
{to: {name: 'homepage'}, label: "Polétání.cz"}, {to: {name: 'homepage'}, label: "Polétání.cz"},
{to: {name: "publicProfileDetail", params: {username: props.username}}, label: `@${props.username}`}, {to: {name: "publicProfileDetail", params: {username: props.pilot.publicUsername}}, label: `@${props.pilot.publicUsername}`},
{label: "Události"} {label: "Události"}
]); ]);
useEventsStore().fetch(props.username);
}
);
useEventsStore().fetch(props.pilot.publicUsername);
const {isQueryRunning, events} = storeToRefs(useEventsStore()); const {isQueryRunning, events} = storeToRefs(useEventsStore());
</script> </script>
<template> <template>
<PublicFlightEventSwitcher :username="username"/> <PublicFlightEventSwitcher :username="pilot.publicUsername"/>
<FlightsListSkeleton v-if="isQueryRunning"/> <FlightsListSkeleton v-if="isQueryRunning"/>
@@ -48,7 +44,7 @@ const {isQueryRunning, events} = storeToRefs(useEventsStore());
<template v-else> <template v-else>
<EventsList <EventsList
:items="events" :items="events"
:route-detail="{name: 'publicEventDetail', params: {username: username}}" :route-detail="{name: 'publicEventDetail', params: {username: pilot.publicUsername}}"
class="q-mb-md" class="q-mb-md"
/> />
</template> </template>
@@ -1,29 +1,45 @@
<script>
import {useFlightsStore} from "stores/Flights";
export default {
preFetch({currentRoute}) {
return useFlightsStore().fetchDetail(currentRoute.params.id, currentRoute.params.username)
}
}
</script>
<script setup> <script setup>
import {onMounted, watch} from "vue";
import {useRoute} from "vue-router";
import FlightTrackMap from "components/flights/FlightTrackMap.vue"; import FlightTrackMap from "components/flights/FlightTrackMap.vue";
import FlightBasicInfo from "components/flights/FlightBasicInfo.vue"; import FlightBasicInfo from "components/flights/FlightBasicInfo.vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import FlightDetailSkeleton from "components/flights/FlightDetailSkeleton.vue"; import FlightDetailSkeleton from "components/flights/FlightDetailSkeleton.vue";
import PhotoGallery from "components/photos/PhotoGallery.vue"; import PhotoGallery from "components/photos/PhotoGallery.vue";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import {useFlightsStore} from "stores/Flights";
import {flightName} from "src/composables/flight"; import {flightName} from "src/composables/flight";
import {useRoute} from "vue-router";
import {useUserStore} from "stores/User";
import {useMeta} from "quasar";
import {metaData} from "src/composables/meta";
const {isQueryRunning, flight} = storeToRefs(useFlightsStore());
const routeParams = useRoute().params; const routeParams = useRoute().params;
onMounted(() => {
useFlightsStore().fetchDetail(routeParams.id, routeParams.username).then(f => { const pilot = storeToRefs(useUserStore()).publicUser;
const flightNameFormatted = flightName(f);
const flightNameFormatted = flightName(flight.value);
useAppStore().setPageTitle(flightNameFormatted); useAppStore().setPageTitle(flightNameFormatted);
useAppStore().setBreadcrumbs([ useAppStore().setBreadcrumbs([
{to: {name: 'homepage'}, label: "Polétání.cz"}, {to: {name: 'homepage'}, label: "Polétání.cz"},
{to: {name: "publicProfileDetail", params: {username: routeParams.username}}, label: `@${routeParams.username}`}, {to: {name: "publicProfileDetail", params: {username: routeParams.username}}, label: `@${routeParams.username}`},
{label: flightNameFormatted} {label: flightNameFormatted}
]); ]);
})
})
const {isQueryRunning, flight} = storeToRefs(useFlightsStore()); useMeta(() => metaData({
title: `${flightNameFormatted} - ${pilot.value.name}`,
keywords: ["let", "výlet", "letadlo", pilot.value.name, flight.value.aircraft.callSign],
description: flight.description,
image: flight.coverPhoto?.url
}
))
</script> </script>
<template> <template>
@@ -55,8 +71,6 @@ const {isQueryRunning, flight} = storeToRefs(useFlightsStore());
<FlightTrackMap :flight="flight"/> <FlightTrackMap :flight="flight"/>
</q-card-section> </q-card-section>
</q-card> </q-card>
</template> </template>
</div> </div>
</template> </template>
@@ -2,42 +2,40 @@
import FlightsListSkeleton from "components/flights/FlightsListSkeleton.vue"; import FlightsListSkeleton from "components/flights/FlightsListSkeleton.vue";
import FlightsList from "components/flights/FlightsList.vue"; import FlightsList from "components/flights/FlightsList.vue";
import PublicFlightEventSwitcher from "components/navigation/PublicFlightEventSwitcher.vue"; import PublicFlightEventSwitcher from "components/navigation/PublicFlightEventSwitcher.vue";
import {onMounted} from "vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import {useUserStore} from "stores/User";
import {useFlightsStore} from "stores/Flights"; import {useFlightsStore} from "stores/Flights";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import {commonKeywords, metaData} from "src/composables/meta";
import {useMeta} from "quasar";
import {useUserStore} from "stores/User";
const props = defineProps({ const props = defineProps({
loggedUser: { pilot: {
type: Object, type: Object,
required: false required: true,
},
username: {
type: String,
required: true
} }
}); });
onMounted(() => { useMeta(() => metaData({
useUserStore().fetchPublicUserDetail(props.username).then(user => { title: `${props.pilot.publicUsername} - Lety`,
useAppStore().setPageTitle(user.publicUsername); keywords: [...commonKeywords, "pilot", props.pilot.publicUsername, props.pilot.name],
description: `Výlety publikované pilotem ${props.pilot.name}. ${props.pilot.publicUsername}: ${props.pilot.description}`,
image: props.pilot.titleImageUrl
}));
useAppStore().setPageTitle(props.pilot.publicUsername);
useAppStore().setBreadcrumbs([ useAppStore().setBreadcrumbs([
{to: {name: 'homepage'}, label: "Polétání.cz"}, {to: {name: 'homepage'}, label: "Polétání.cz"},
{label: `@${user.publicUsername}`}, {label: `@${props.pilot.publicUsername}`},
]) ])
})
useFlightsStore().fetch(props.username);
});
useFlightsStore().fetch(props.pilot.publicUsername);
const {flights, isQueryRunning: areFlightsLoading} = storeToRefs(useFlightsStore()); const {flights, isQueryRunning: areFlightsLoading} = storeToRefs(useFlightsStore());
const {publicUser: pilot} = storeToRefs(useUserStore()); const loggedUser = storeToRefs(useUserStore()).user;
</script> </script>
<template> <template>
<PublicFlightEventSwitcher :username="username" /> <PublicFlightEventSwitcher :username="pilot?.publicUsername"/>
<FlightsListSkeleton v-if="areFlightsLoading"/> <FlightsListSkeleton v-if="areFlightsLoading"/>
@@ -48,8 +46,8 @@ const {publicUser: pilot} = storeToRefs(useUserStore());
<template v-else> <template v-else>
<FlightsList <FlightsList
:items="flights" :items="flights"
:displayVisibilityInfo="loggedUser.id === pilot.id" :displayVisibilityInfo="loggedUser?.id === pilot?.id"
:route-detail="{name: 'publicFlightDetail', params: {username: username}}" :route-detail="{name: 'publicFlightDetail', params: {username: pilot?.publicUsername}}"
class="q-mb-md" class="q-mb-md"
/> />
</template> </template>
-1
View File
@@ -1,7 +1,6 @@
import PlainLayout from "layouts/PlainLayout.vue"; import PlainLayout from "layouts/PlainLayout.vue";
const routes = [ const routes = [
{path: '/test', name: "test", component: () => import('pages/public/TestPage.vue'), meta: {layout: PlainLayout}},
{path: '/login', name: "login", component: () => import('pages/public/LoginPage.vue'), meta: {layout: PlainLayout}}, {path: '/login', name: "login", component: () => import('pages/public/LoginPage.vue'), meta: {layout: PlainLayout}},
{path: '/registrace', name: "registration", component: () => import('pages/public/RegisterPage.vue'), meta: {layout: PlainLayout}}, {path: '/registrace', name: "registration", component: () => import('pages/public/RegisterPage.vue'), meta: {layout: PlainLayout}},
{path: '/zapomenute-heslo', name: "forgottenPassword", component: () => import('pages/public/RegisterPage.vue'), meta: {layout: PlainLayout}}, {path: '/zapomenute-heslo', name: "forgottenPassword", component: () => import('pages/public/RegisterPage.vue'), meta: {layout: PlainLayout}},
+4 -3
View File
@@ -7,9 +7,9 @@ export const useAppStore = defineStore('App', () => {
const appStatus = ref(200); // TODO: toto nejspis nebude potreba! const appStatus = ref(200); // TODO: toto nejspis nebude potreba!
const resetAfterRouteChange = () => { const resetAfterRouteChange = () => {
setPageTitle(""); pageTitle.value = null;
setBreadcrumbs([]);
appStatus.value = 200; appStatus.value = 200;
breadcrumbs.value = []
}; };
const setPageTitle = (value) => { const setPageTitle = (value) => {
@@ -22,6 +22,7 @@ export const useAppStore = defineStore('App', () => {
} }
return { return {
pageTitle, breadcrumbs, appStatus, resetAfterRouteChange, setPageTitle, setBreadcrumbs pageTitle, breadcrumbs, appStatus,
resetAfterRouteChange, setPageTitle, setBreadcrumbs
} }
}) })
+2 -2
View File
@@ -10,11 +10,11 @@ export const useEventsStore = defineStore('Events', () => {
const isMutationRunning = ref(false); const isMutationRunning = ref(false);
const isQueryRunning = ref(false); const isQueryRunning = ref(false);
const fetchDetail = (id) => { const fetchDetail = (id, username) => {
return query({ return query({
isQueryRunning, isQueryRunning,
query: EVENT_DETAIL, query: EVENT_DETAIL,
variables: {id}, variables: {id, username},
}).then(item => event.value = item); }).then(item => event.value = item);
}; };
-51
View File
@@ -3,7 +3,6 @@ import {FLIGHT_DETAIL, FLIGHTS_LIST} from "src/graphql/queries/flights";
import {EDIT_FLIGHT, CREATE_FLIGHT, DELETE_FLIGHT} from "src/graphql/mutations/flight"; import {EDIT_FLIGHT, CREATE_FLIGHT, DELETE_FLIGHT} from "src/graphql/mutations/flight";
import {deleteFromItems, mutate, query, updateInItems} from "stores/helpers"; import {deleteFromItems, mutate, query, updateInItems} from "stores/helpers";
import {ref} from "vue"; import {ref} from "vue";
import {DELETE_AIRCRAFT} from "src/graphql/mutations/aircrafts";
export const useFlightsStore = defineStore('Flights', () => { export const useFlightsStore = defineStore('Flights', () => {
const flight = ref(null); const flight = ref(null);
@@ -58,57 +57,7 @@ export const useFlightsStore = defineStore('Flights', () => {
}).then(item => deleteFromItems(flights.value, 'id', item)) }).then(item => deleteFromItems(flights.value, 'id', item))
} }
return { return {
flight, flights, isMutationRunning, isQueryRunning, fetchDetail, fetch, create, update, destroy flight, flights, isMutationRunning, isQueryRunning, fetchDetail, fetch, create, update, destroy
} }
//
// actions: {
// create(formData) {
// delete formData['id'];
// return apolloWrapper.mutate(CREATE_FLIGHT, {input: formData}).then(({data}) => {
// const flightId = data.createFlight.id;
//
// // pitomy ohack kvuli tomu, ze se mi nedari dostat z API rovnou informace o nove vytvorenem letadle
// this.fetchDetail(flightId).then(flight => {
// this.items.push(flight);
// })
//
// return data.createFlight;
// })
// },
// update(id, formData) {
// this.isMutationRunning = true;
// return apolloWrapper.mutate(EDIT_FLIGHT, {id: id, input: formData}).then(({data}) => {
// const flight = data.editFlight;
// updateInItems(this.items, "id", flight);
//
// return flight;
// }).finally(() => this.isMutationRunning = false);
// },
// 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 = null) {
// return apolloWrapper
// .query(FLIGHTS_LIST, {username})
// .then((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}) => {
//
// const idx = this.items.indexOf(this.items.find(f => f.id === id));
// this.items.splice(idx, 1);
//
// return data.deleteFlight;
// })
// },
// },
}); });
+5 -2
View File
@@ -12,9 +12,10 @@ export const useUserStore = defineStore('LoggedUser', () => {
const isMutationRunning = ref(false); const isMutationRunning = ref(false);
const isRefreshing = ref(false); const isRefreshing = ref(false);
const fetchLoggedUserDetail = () => query({ const fetchLoggedUserDetail = ({refresh}) => query({
isQueryRunning, isQueryRunning,
query: LOGGED_USER_DETAIL query: LOGGED_USER_DETAIL,
refreshUnauthorized: refresh || false,
}).then(item => { }).then(item => {
user.value = item; user.value = item;
return item; return item;
@@ -67,6 +68,8 @@ export const useUserStore = defineStore('LoggedUser', () => {
}; };
const getAccessToken = () => { const getAccessToken = () => {
if (typeof localStorage === 'undefined') return null;
const tokenValidity = localStorage.getItem("accessTokenValidity"); const tokenValidity = localStorage.getItem("accessTokenValidity");
const now = new Date().getTime() / 1000; const now = new Date().getTime() / 1000;
+9 -2
View File
@@ -17,15 +17,22 @@ export const deleteFromItems = (items, key, value) => {
items.splice(idx, 1); items.splice(idx, 1);
} }
export const query = ({isQueryRunning, query, variables}) => { export const query = ({isQueryRunning, query, variables, refreshUnauthorized}) => {
if (variables && variables.id) { if (variables && variables.id) {
variables.id = parseInt(variables.id); variables.id = parseInt(variables.id);
} }
query._refreshUnauthorizedUser = refreshUnauthorized;
isQueryRunning.value = true; isQueryRunning.value = true;
const queryName = query.definitions[0].selectionSet.selections[0].name.value; const queryName = query.definitions[0].selectionSet.selections[0].name.value;
return apolloWrapper.query(query, variables) return apolloWrapper.query(query, variables)
.then((result) => result.data[queryName]) .then((result) => {
if (!result || result.error || !result.data) {
return null;
}
return result.data[queryName]
})
.finally(() => isQueryRunning.value = false); .finally(() => isQueryRunning.value = false);
} }