diff --git a/docker-compose.yml b/docker-compose.yml index f0651c7..37bbaee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: ## command: ls -l /app app: - network_mode: host +# network_mode: host build: context: . target: "develop-stage" @@ -33,11 +33,12 @@ services: - "9001:9100" volumes: - ./src:/app/src + - ./src-ssr:/app/src-ssr - ./public:/app/public - - ./node_modules:/app/node_modules:delegated +# - ./node_modules:/app/node_modules:delegated command: quasar dev -m ssr environment: - API_URL: "http://localhost:8000" + API_URL: "http://127.0.0.1:8000" # nginx: # image: diff --git a/package.json b/package.json index fef578e..bcd699f 100644 --- a/package.json +++ b/package.json @@ -15,28 +15,28 @@ "@vue/apollo-composable": "4.0.0-beta.2", "@vue/apollo-option": "^4.0.0-beta.5", "apollo-upload-client": "^17.0.0", - "axios": "^1.4.0", - "chart.js": "^4.4.0", + "axios": "^1.6.2", + "chart.js": "^4.4.1", "graphql": "16.6.0", "graphql-tag": "2.12.6", "photoswipe": "^5.4.2", "photoswipe-dynamic-caption-plugin": "^1.2.7", "pinia": "^2.0.33", "quasar": "^2.14.1", - "vue": "^3.2.47", + "vue": "^3.3.11", "vue-chartjs": "^5.2.0", "vue-picture-cropper": "^0.7.0", - "vue-router": "^4.1.6", + "vue-router": "^4.2.5", "vue3-google-map": "^0.15.0" }, "devDependencies": { "@quasar/app-vite": "^1.7.1", - "autoprefixer": "^10.4.2", - "eslint": "^8.10.0", - "eslint-config-prettier": "^8.1.0", - "eslint-plugin-vue": "^9.0.0", + "autoprefixer": "^10.4.16", + "eslint": "^8.55.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-vue": "^9.19.2", "postcss": "^8.4.14", - "prettier": "^2.5.1" + "prettier": "^2.8.8" }, "engines": { "npm": ">= 6.13.4", diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..cfc6158 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Disallow: /login/ +Disallow: /moje/ diff --git a/src/boot/apollo.js b/src/boot/apollo.js index e64d38d..b502454 100644 --- a/src/boot/apollo.js +++ b/src/boot/apollo.js @@ -1,17 +1,17 @@ import {ApolloClient, ApolloLink, fromPromise, InMemoryCache} from '@apollo/client/core' import {onError} from "@apollo/client/link/error"; -import {useUserStore} from "stores/User"; import {useAppStore} from "stores/App"; import {Notify} from "quasar"; import {errorObject, warningObject} from "src/composables/dialog"; import {createUploadLink} from "apollo-upload-client"; +import {useAuthStore} from "stores/Auth"; // import createUploadLink from "apollo-upload-client/createUploadLink.mjs"; const handleRefreshToken = (store, router, forward, operation) => { if (!operation.query?._refreshUnauthorizedUser) return; console.log("refreshing token, ", operation.query); - return fromPromise(useUserStore(store).refreshAccessToken() + return fromPromise(useAuthStore(store).refreshAccessToken() .catch((error) => { Notify.create(warningObject("Tvoje přihlášení vypřešlo. Přihlaš se prosím znovu.")); router.push({name: "login"}) @@ -39,8 +39,7 @@ export default (({store, router}) => { }) const authLink = new ApolloLink((operation, forward) => { - const accessToken = useUserStore(store).getAccessToken(); - + const accessToken = useAuthStore(store).getAccessToken(); if (accessToken) { operation.setContext({ headers: {"Authorization": `Bearer ${accessToken}`}, @@ -50,7 +49,6 @@ export default (({store, router}) => { }); const errorLink = onError(({graphQLErrors, networkError, operation, forward}) => { - console.log(networkError, graphQLErrors); if (networkError?.statusCode === 401) { return handleRefreshToken(store, router, forward, operation); } diff --git a/src/boot/axios.js b/src/boot/axios.js index 31d1990..4736942 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -1,6 +1,6 @@ import {boot} from 'quasar/wrappers' import axios from 'axios' -import {useUserStore} from "stores/User"; +import {useAuthStore} from "stores/Auth"; const api = axios.create({ @@ -15,7 +15,7 @@ const api = axios.create({ export default boot(({store}) => { api.interceptors.request.use((config) => { - const token = useUserStore(store).getAccessToken(); + const token = useAuthStore(store).getAccessToken(); if (token) { config.headers['Authorization'] = `Bearer ${token}` } @@ -33,8 +33,7 @@ export default boot(({store}) => { async function (error) { const originalRequest = error.config; if (error.response.status === 401 && !['/refresh', '/login'].includes(originalRequest.url)) { - const newToken = await useUserStore(store).refreshAccessToken().catch(err => { - console.error(err); + const newToken = await useAuthStore(store).refreshAccessToken().catch(err => { return Promise.reject("Logged out"); }) api.defaults.headers.common['Authorization'] = 'Bearer ' + newToken; diff --git a/src/components/AppFooter.vue b/src/components/AppFooter.vue new file mode 100644 index 0000000..a828130 --- /dev/null +++ b/src/components/AppFooter.vue @@ -0,0 +1,38 @@ + + + + + + diff --git a/src/components/LoadMoreList.vue b/src/components/LoadMoreList.vue new file mode 100644 index 0000000..63a9eee --- /dev/null +++ b/src/components/LoadMoreList.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/components/aircrafts/AircraftsList.vue b/src/components/aircrafts/AircraftsList.vue index de1c0af..13e55ad 100644 --- a/src/components/aircrafts/AircraftsList.vue +++ b/src/components/aircrafts/AircraftsList.vue @@ -1,46 +1,68 @@ diff --git a/src/components/flights/FlightsList.vue b/src/components/flights/FlightsList.vue index 62503a6..1739a23 100644 --- a/src/components/flights/FlightsList.vue +++ b/src/components/flights/FlightsList.vue @@ -2,6 +2,9 @@ import {minutesToHumanReadable} from "src/composables/datetimeUtils"; import {flightName} from "src/composables/flight"; import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue"; +import FlightsListSkeleton from "components/flights/skeletons/FlightsListSkeleton.vue"; +import {computed} from "vue"; +import LoadMoreList from "components/LoadMoreList.vue"; const props = defineProps({ items: { @@ -12,6 +15,20 @@ const props = defineProps({ type: Boolean, required: true }, + totalItemsCount: { + type: Number, + required: true + }, + itemsPerPage: { + type: Number, + required: false, + default: 5, + }, + isQueryRunning: { + type: Boolean, + required: false, + default: false + } }); const routeDetail = (flight) => { @@ -20,52 +37,75 @@ const routeDetail = (flight) => { else return {name: "flightDetail", params: {id: flight.id}} }; + +const pluralize = (count, singular, plural, pluralMore) => { + if (count === 1) return singular; + else if (count < 5) return plural; + else return pluralMore; +} +const loadMoreCount = computed(() => { + const remaining = props.totalItemsCount - props.items.length; + + return Math.min(props.itemsPerPage, remaining); +}); + diff --git a/src/components/flights/skeletons/FlightsListSkeleton.vue b/src/components/flights/skeletons/FlightsListSkeleton.vue index 8ba4cfa..3299dcb 100644 --- a/src/components/flights/skeletons/FlightsListSkeleton.vue +++ b/src/components/flights/skeletons/FlightsListSkeleton.vue @@ -1,10 +1,18 @@