Uprava error stranek, indexu
@@ -36,7 +36,7 @@ services:
|
||||
- ./src-ssr:/app/src-ssr
|
||||
- ./public:/app/public
|
||||
# - ./node_modules:/app/node_modules:delegated
|
||||
command: quasar dev -m ssr
|
||||
command: quasar dev # -m ssr
|
||||
environment:
|
||||
API_URL: "http://127.0.0.1:8000"
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 947 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 841 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 233 KiB |
@@ -30,7 +30,8 @@ export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
||||
|
||||
// Should reach here only if no "catch-all" route
|
||||
// is defined in /src/routes
|
||||
res.status(404).send('404 | Page Not Found')
|
||||
// res.status(404).send('404 | Page Not Found')
|
||||
res.redirect(resolve.urlPath('404'))
|
||||
} else if (process.env.DEV) {
|
||||
// well, we treat any other code as error;
|
||||
// if we're in dev mode, then we can use Quasar CLI
|
||||
@@ -39,6 +40,7 @@ export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
||||
|
||||
// serve.error is available on dev only
|
||||
serve.error({ err, req, res })
|
||||
|
||||
} else {
|
||||
// we're in production, so we should have another method
|
||||
// to display something to the client when we encounter an error
|
||||
@@ -47,8 +49,7 @@ export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
||||
|
||||
// Render Error Page on production or
|
||||
// create a route (/src/routes) for an error page and redirect to it
|
||||
res.status(500).send('500 | Internal Server Error')
|
||||
// console.error(err.stack)
|
||||
res.redirect(resolve.urlPath('500'))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import {useRoute} from "vue-router";
|
||||
import {computed} from "vue";
|
||||
import ErrorNotFound from "pages/ErrorNotFound.vue";
|
||||
import ErrorNotFound from "pages/errors/ErrorNotFound.vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useAppStore} from "stores/App";
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 219 KiB |
@@ -55,9 +55,7 @@ export default (({store, router}) => {
|
||||
|
||||
if (graphQLErrors) {
|
||||
for (const error of graphQLErrors) {
|
||||
if (error.message === "Not found") {
|
||||
useAppStore(store).appStatus = 404;
|
||||
} else if (error.message.includes("401") || error.message.includes("Not authorized")) {
|
||||
if (error.message.includes("401") || error.message.includes("Not authorized")) {
|
||||
return handleRefreshToken(store, router, forward, operation);
|
||||
} else {
|
||||
Notify.create(errorObject("API error: " + error.message));
|
||||
@@ -78,9 +76,13 @@ export default (({store, router}) => {
|
||||
})
|
||||
.then((response) => response)
|
||||
.catch(err => {
|
||||
if (!err.toString().includes("401") && !err.toString().includes("Not authorized"))
|
||||
const errStr = err.toString();
|
||||
if (errStr.includes('Not found')) {
|
||||
useAppStore(store).appStatus = 404;
|
||||
}
|
||||
else if (!errStr.includes("401") && !errStr.includes("Not authorized"))
|
||||
console.error("Error during query: ", err)
|
||||
return err;
|
||||
return Promise.reject(err);
|
||||
})
|
||||
};
|
||||
apolloWrapper.mutate = (mutation, variables) => {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row q-mx-md">
|
||||
<div class="col-12 col-md-6 col-lg-6 self-center info-box">
|
||||
<div class="text-h3 text-center q-my-lg">Fotogalerie ze vzduchu</div>
|
||||
|
||||
<p>Polétání.cz je aplikace pro piloty, kteří si chtějí spravovat fotografie ze svých výletů, zobrazovat je na mapě a ukládat si trasy svých výletů.</p>
|
||||
|
||||
<div class="text-center q-my-lg">
|
||||
<q-btn size="xl" :to="{name: 'registration'}" rounded color="secondary">Založit účet</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-6 text-center">
|
||||
<div>
|
||||
<q-img src="hp/mobile-preview-200.webp" width="200px" class="q-ma-lg" fit="contain" alt="Nahled aplikace"/>
|
||||
<q-img src="hp/desktop-preview-350.webp" height="310px" width="350px" fit="contain" class="xs-hide sm-hide" alt="Nahled aplikace"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.info-box {
|
||||
font-size: 150%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,8 @@
|
||||
import gql from "graphql-tag";
|
||||
import {FRAGMENT_USER_FULL_INFO} from "src/graphql/fragments/user";
|
||||
|
||||
export const USER_DETAIL = gql`
|
||||
query userDetail($username: String!) {
|
||||
export const PILOT_DETAIL = gql`
|
||||
query pilotDetail($username: String!) {
|
||||
user(username: $username) {
|
||||
...userFullInfo
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
|
||||
export default {
|
||||
preFetch() {
|
||||
// todo: nevim proc tu nefunguje refresh, neposila se credentials
|
||||
return useLoggedUserStore().fetchLoggedUserDetail({refresh: false});
|
||||
return useLoggedUserStore().fetchLoggedUserDetail({refresh: false}).catch(e => {});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -51,6 +50,6 @@ import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
|
||||
import BreadcrumbsSkeleton from "components/navigation/BreadcrumbsSkeleton.vue";
|
||||
import AppFooter from "components/AppFooter.vue";
|
||||
|
||||
const {pageTitle, breadcrumbs} = storeToRefs(useAppStore());
|
||||
const {pageTitle, breadcrumbs, appStatus} = storeToRefs(useAppStore());
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
</script>
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
<div class="fullscreen text-center q-pa-md flex flex-center">
|
||||
<div>
|
||||
|
||||
<q-img src="/plane-crash.svg" width="75%" />
|
||||
<div style="font-size: 10vh">
|
||||
Tohle jsme tu nenašli...
|
||||
</div>
|
||||
|
||||
<div style="font-size: 20vh">
|
||||
404
|
||||
<div>
|
||||
... zkus si srovnat mapu podle kurzu, najít významnou linii bezpečně před sebou a tu vyhlížej...
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="fullscreen text-center q-pa-md flex flex-center">
|
||||
<div>
|
||||
|
||||
<q-img src="/plane-crash.svg" width="70%" />
|
||||
|
||||
<div style="font-size: 15vh">
|
||||
Uups…
|
||||
</div>
|
||||
Něco tu havarovalo...
|
||||
|
||||
Pro další letecké nehody zatím koukni <a href="https://uzpln.cz/zpravy-ln" target="_blank">sem</a>. Za chvíli to tu snad bude fungovat.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServerError'
|
||||
})
|
||||
</script>
|
||||
@@ -7,6 +7,7 @@ import {onMounted} from "vue";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
import {storeToRefs} from "pinia";
|
||||
import TimelineDescription from "components/indexPage/TimelineDescription.vue";
|
||||
import IntroBlock from "components/indexPage/IntroBlock.vue";
|
||||
|
||||
useMeta(() => metaData({
|
||||
title: "Polétání.cz",
|
||||
@@ -21,7 +22,7 @@ const {flights, isQueryRunning} = storeToRefs(useFlightsStore());
|
||||
<template>
|
||||
<q-layout view="lHh Lpr fff">
|
||||
|
||||
<q-header class="bg-semi-transparent text-black" elevated>
|
||||
<q-header>
|
||||
<q-toolbar>
|
||||
<q-toolbar-title>
|
||||
Polétání.cz
|
||||
@@ -34,22 +35,8 @@ const {flights, isQueryRunning} = storeToRefs(useFlightsStore());
|
||||
|
||||
<q-page-container class="q-py-none">
|
||||
<q-page>
|
||||
<div class="row q-mx-md">
|
||||
<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-h3 text-center q-my-lg">Fotogalerie ze vzduchu</div>
|
||||
|
||||
<p>Polétání.cz je aplikace pro piloty, kteří si chtějí spravovat fotografie ze svých výletů, zobrazovat je na mapě a ukládat si trasy svých výletů.</p>
|
||||
|
||||
<div class="text-center q-my-lg">
|
||||
<q-btn size="xl" :to="{name: 'registration'}" rounded color="secondary">Založit účet</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 text-center">
|
||||
<q-img src="hp/app_screenshot.png" fit="contain" class="app-screen" alt="Nahled aplikace"/>
|
||||
</div>
|
||||
</div>
|
||||
<IntroBlock/>
|
||||
|
||||
<h3 class="text-center">Poslední lety</h3>
|
||||
<LatestFlightsSlider :items="flights"/>
|
||||
@@ -59,26 +46,9 @@ const {flights, isQueryRunning} = storeToRefs(useFlightsStore());
|
||||
<TimelineDescription/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
<AppFooter/>
|
||||
</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>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script>
|
||||
import {usePilotsStore} from "stores/Pilots";
|
||||
import {useAppStore} from "stores/App";
|
||||
|
||||
export default {
|
||||
preFetch({store, currentRoute}) {
|
||||
return usePilotsStore(store).fetchPublicPilotDetail(currentRoute.params.username);
|
||||
return usePilotsStore(store).fetchPublicPilotDetail(currentRoute.params.username).catch(e => {});
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -43,8 +43,8 @@ useAppStore().setBreadcrumbs([
|
||||
useMeta(() => metaData({
|
||||
title: `${flightNameFormatted} - ${props.pilot.name}`,
|
||||
keywords: ["let", "výlet", "letadlo", props.pilot.name, flight.value.aircraft.callSign],
|
||||
description: flight.description,
|
||||
image: flight.titlePhoto?.url
|
||||
description: flight.value.description,
|
||||
image: flight.value.titlePhoto?.url
|
||||
}
|
||||
));
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,7 @@ export default route(function ({ store }) {
|
||||
history: createHistory(process.env.VUE_ROUTER_BASE)
|
||||
})
|
||||
|
||||
Router.afterEach(async (to, from) => {
|
||||
Router.beforeEach(async (to, from) => {
|
||||
if (from.name !== to.name) {
|
||||
useAppStore(store).resetAfterRouteChange();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ const routes = [
|
||||
{path: 'letadlo/:id', name: "publicAircraftDetail", component: () => import('pages/public/aircraft/AircraftDetailPage.vue')},
|
||||
{path: 'mista', name: "publicPOIs", component: () => import('pages/public/poi/PointsOfInterestPage.vue')},
|
||||
{path: 'mista/:id', name: "publicPOIDetail", component: () => import('pages/public/poi/PointOfInterestDetailPage.vue')},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -69,8 +68,16 @@ const routes = [
|
||||
|
||||
]
|
||||
},
|
||||
{path: '/refresh', name: "profileRefresh", component: () => import('pages/profile/ProfileRefreshPage.vue')},
|
||||
|
||||
{
|
||||
path: '/errors',
|
||||
children: [
|
||||
{path: '404', name: "404", component: () => import('pages/errors/ErrorNotFound.vue')},
|
||||
{path: '500', name: "500", component: () => import('pages/errors/ServerError.vue')},
|
||||
]
|
||||
},
|
||||
|
||||
{path: '/refresh', name: "profileRefresh", component: () => import('pages/profile/ProfileRefreshPage.vue')},
|
||||
|
||||
|
||||
// Always leave this as last one,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {ref} from "vue";
|
||||
import {paginationOffset} from "src/composables/pagination";
|
||||
import {paginationLimit, paginationOffset} from "src/composables/pagination";
|
||||
|
||||
export const useAppStore = defineStore('App', () => {
|
||||
const pageTitle = ref(null);
|
||||
@@ -8,10 +8,13 @@ export const useAppStore = defineStore('App', () => {
|
||||
const appStatus = ref(200); // TODO: toto nejspis nebude potreba!
|
||||
|
||||
const resetAfterRouteChange = () => {
|
||||
console.log("reset after route change");
|
||||
pageTitle.value = null;
|
||||
appStatus.value = 200;
|
||||
breadcrumbs.value = null;
|
||||
|
||||
// reset strankovani po prechodu na strankach
|
||||
paginationLimit.value = 10;
|
||||
paginationOffset.value = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {api} from "boot/axios";
|
||||
import {EDIT_USER} from "src/graphql/mutations/user";
|
||||
import {LOGGED_USER_DETAIL, USER_DETAIL} from "src/graphql/queries/user";
|
||||
import {LOGGED_USER_DETAIL, PILOT_DETAIL} from "src/graphql/queries/user";
|
||||
import {ref} from "vue";
|
||||
import {mutate, query} from "stores/helpers";
|
||||
import {cookies} from "boot/cookies";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {api} from "boot/axios";
|
||||
import {EDIT_USER} from "src/graphql/mutations/user";
|
||||
import {LOGGED_USER_DETAIL, USER_DETAIL} from "src/graphql/queries/user";
|
||||
import {LOGGED_USER_DETAIL, PILOT_DETAIL} from "src/graphql/queries/user";
|
||||
import {ref} from "vue";
|
||||
import {mutate, query} from "stores/helpers";
|
||||
import {cookies} from "boot/cookies";
|
||||
@@ -12,7 +12,7 @@ export const usePilotsStore = defineStore('Pilots', () => {
|
||||
|
||||
const fetchPublicPilotDetail = (username) => query({
|
||||
isQueryRunning,
|
||||
query: USER_DETAIL,
|
||||
query: PILOT_DETAIL,
|
||||
variables: {username}
|
||||
}).then(item => {
|
||||
pilot.value = item;
|
||||
|
||||
@@ -40,6 +40,7 @@ export const query = ({isQueryRunning, query, variables, refreshUnauthorized}) =
|
||||
}
|
||||
return result.data[queryName]
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => isQueryRunning.value = false);
|
||||
}
|
||||
|
||||
|
||||