diff --git a/Dockerfile b/Dockerfile
index 7113f05..d854ec1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,12 +13,15 @@ FROM develop-stage as build-stage
#ENV VUE_API_URL $API_URL
RUN yarn --production=false
-RUN quasar build
+RUN quasar build -m ssr
# 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
+#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
-CMD ["nginx", "-g", "daemon off;"]
diff --git a/docker-compose.yml b/docker-compose.yml
index 8da81aa..da7a96a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -29,11 +29,12 @@ services:
target: "develop-stage"
ports:
- "9000:9000"
+ - "9001:9100"
volumes:
- ./src:/app/src
- ./public:/app/public
- ./node_modules:/app/node_modules:delegated
- command: quasar dev
+ command: quasar dev -m ssr
environment:
API_URL: "http://localhost:8000"
diff --git a/quasar.config.js b/quasar.config.js
index b657d33..c1c034f 100644
--- a/quasar.config.js
+++ b/quasar.config.js
@@ -30,7 +30,7 @@ module.exports = configure(function (ctx) {
// https://v2.quasar.dev/quasar-cli/boot-files
boot: [
"axios",
- "chartJs",
+ "chartJs"
],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
@@ -135,10 +135,10 @@ module.exports = configure(function (ctx) {
// https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
- // ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
+ ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
- // extendSSRWebserverConf (esbuildConf) {},
+ extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false,
diff --git a/src-ssr/middlewares/render.js b/src-ssr/middlewares/render.js
new file mode 100644
index 0000000..2397f83
--- /dev/null
+++ b/src-ssr/middlewares/render.js
@@ -0,0 +1,55 @@
+import { ssrMiddleware } from 'quasar/wrappers'
+
+// This middleware should execute as last one
+// since it captures everything and tries to
+// render the page with Vue
+
+export default ssrMiddleware(({ app, resolve, render, serve }) => {
+ // we capture any other Express route and hand it
+ // over to Vue and Vue Router to render our page
+ app.get(resolve.urlPath('*'), (req, res) => {
+ res.setHeader('Content-Type', 'text/html')
+
+ render(/* the ssrContext: */ { req, res })
+ .then(html => {
+ // now let's send the rendered html to the client
+ res.send(html)
+ })
+ .catch(err => {
+ // oops, we had an error while rendering the page
+
+ // we were told to redirect to another URL
+ if (err.url) {
+ if (err.code) {
+ res.redirect(err.code, err.url)
+ } else {
+ res.redirect(err.url)
+ }
+ } else if (err.code === 404) {
+ // hmm, Vue Router could not find the requested route
+
+ // Should reach here only if no "catch-all" route
+ // is defined in /src/routes
+ res.status(404).send('404 | Page Not Found')
+ } 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
+ // to display a nice error page that contains the stack
+ // and other useful information
+
+ // 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
+ // (for security reasons, it's not ok to display the same wealth
+ // of information as we do in development)
+
+ // 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)
+ }
+ })
+ })
+})
diff --git a/src-ssr/server.js b/src-ssr/server.js
new file mode 100644
index 0000000..b3594d9
--- /dev/null
+++ b/src-ssr/server.js
@@ -0,0 +1,136 @@
+/**
+ * More info about this file:
+ * https://v2.quasar.dev/quasar-cli-vite/developing-ssr/ssr-webserver
+ *
+ * Runs in Node context.
+ */
+
+/**
+ * Make sure to yarn add / npm install (in your project root)
+ * anything you import here (except for express and compression).
+ */
+import express from 'express'
+import compression from 'compression'
+import {
+ ssrClose,
+ ssrCreate,
+ ssrListen,
+ ssrRenderPreloadTag,
+ ssrServeStaticContent
+} from 'quasar/wrappers'
+
+/**
+ * Create your webserver and return its instance.
+ * If needed, prepare your webserver to receive
+ * connect-like middlewares.
+ *
+ * Should NOT be async!
+ */
+export const create = ssrCreate((/* { ... } */) => {
+ const app = express()
+
+ // attackers can use this header to detect apps running Express
+ // and then launch specifically-targeted attacks
+ app.disable('x-powered-by')
+
+ // place here any middlewares that
+ // absolutely need to run before anything else
+ if (process.env.PROD) {
+ app.use(compression())
+ }
+
+ return app
+})
+
+/**
+ * You need to make the server listen to the indicated port
+ * and return the listening instance or whatever you need to
+ * close the server with.
+ *
+ * The "listenResult" param for the "close()" definition below
+ * is what you return here.
+ *
+ * For production, you can instead export your
+ * handler for serverless use or whatever else fits your needs.
+ */
+export const listen = ssrListen(async ({ app, port, isReady }) => {
+ await isReady()
+ return app.listen(port, () => {
+ if (process.env.PROD) {
+ console.log('Server listening at port ' + port)
+ }
+ })
+})
+
+/**
+ * Should close the server and free up any resources.
+ * Will be used on development only when the server needs
+ * to be rebooted.
+ *
+ * Should you need the result of the "listen()" call above,
+ * you can use the "listenResult" param.
+ *
+ * Can be async.
+ */
+export const close = ssrClose(({ listenResult }) => {
+ return listenResult.close()
+})
+
+const maxAge = process.env.DEV
+ ? 0
+ : 1000 * 60 * 60 * 24 * 30
+
+/**
+ * Should return middleware that serves the indicated path
+ * with static content.
+ */
+export const serveStaticContent = ssrServeStaticContent((path, opts) => {
+ return express.static(path, {
+ maxAge,
+ ...opts
+ })
+})
+
+const jsRE = /\.js$/
+const cssRE = /\.css$/
+const woffRE = /\.woff$/
+const woff2RE = /\.woff2$/
+const gifRE = /\.gif$/
+const jpgRE = /\.jpe?g$/
+const pngRE = /\.png$/
+
+/**
+ * Should return a String with HTML output
+ * (if any) for preloading indicated file
+ */
+export const renderPreloadTag = ssrRenderPreloadTag((file) => {
+ if (jsRE.test(file) === true) {
+ return ``
+ }
+
+ if (cssRE.test(file) === true) {
+ return ``
+ }
+
+ if (woffRE.test(file) === true) {
+ return ``
+ }
+
+ if (woff2RE.test(file) === true) {
+ return ``
+ }
+
+ if (gifRE.test(file) === true) {
+ return ``
+ }
+
+ if (jpgRE.test(file) === true) {
+ return ``
+ }
+
+ if (pngRE.test(file) === true) {
+ return ``
+ }
+
+ return ''
+})
diff --git a/src-ssr/ssr-flag.d.ts b/src-ssr/ssr-flag.d.ts
new file mode 100644
index 0000000..81a098b
--- /dev/null
+++ b/src-ssr/ssr-flag.d.ts
@@ -0,0 +1,10 @@
+/* eslint-disable */
+// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
+// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
+import "quasar/dist/types/feature-flag";
+
+declare module "quasar/dist/types/feature-flag" {
+ interface QuasarFeatureFlags {
+ ssr: true;
+ }
+}
diff --git a/src/boot/axios.js b/src/boot/axios.js
index 07ffed3..4a2cfee 100644
--- a/src/boot/axios.js
+++ b/src/boot/axios.js
@@ -2,6 +2,7 @@ import {boot} from 'quasar/wrappers'
import axios from 'axios'
import {useUserStore} from "stores/User";
+
const api = axios.create({
baseURL: process.env.API_URL,
withCredentials: true,
@@ -12,37 +13,38 @@ const api = axios.create({
},
})
-api.interceptors.request.use((config) => {
- const token = useUserStore().getAccessToken();
- if (token) {
- config.headers['Authorization'] = `Bearer ${token}`
- }
+export default boot(({app, store}) => {
- if (['/refresh', '/login'].includes(config.url)) {
- delete config.headers['Authorization'];
- }
+ api.interceptors.request.use((config) => {
+ const token = useUserStore(store).getAccessToken();
+ if (token) {
+ config.headers['Authorization'] = `Bearer ${token}`
+ }
- return config;
-})
+ if (['/refresh', '/login'].includes(config.url)) {
+ delete config.headers['Authorization'];
+ }
+
+ return config;
+ })
// Response interceptor for API calls
-api.interceptors.response.use((response) => {
- return response
-}, async function (error) {
- const originalRequest = error.config;
- if (error.response.status === 401 && !['/refresh', '/login'].includes(originalRequest.url)) {
- const newToken = await useUserStore().refreshAccessToken().catch(err => {
- console.error(err);
- return Promise.reject("Logged out");
- })
- api.defaults.headers.common['Authorization'] = 'Bearer ' + newToken;
- return api(originalRequest);
- }
- return Promise.reject(error);
-});
+ api.interceptors.response.use((response) => {
+ return response
+ }, 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);
+ return Promise.reject("Logged out");
+ })
+ api.defaults.headers.common['Authorization'] = 'Bearer ' + newToken;
+ return api(originalRequest);
+ }
+ return Promise.reject(error);
+ });
-export default boot(({app}) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
// app.config.globalProperties.$axios = axios
diff --git a/src/components/aircrafts/AircraftDetailSkeleton.vue b/src/components/aircrafts/AircraftDetailSkeleton.vue
index 98ee401..2368f8f 100644
--- a/src/components/aircrafts/AircraftDetailSkeleton.vue
+++ b/src/components/aircrafts/AircraftDetailSkeleton.vue
@@ -13,7 +13,7 @@ import BasicTextSkeleton from "components/basicSkeletons/BasicTextSkeleton.vue";
-
+
diff --git a/src/components/basicSkeletons/BasicListSkeleton.vue b/src/components/basicSkeletons/BasicListSkeleton.vue
index 995e781..9e9342b 100644
--- a/src/components/basicSkeletons/BasicListSkeleton.vue
+++ b/src/components/basicSkeletons/BasicListSkeleton.vue
@@ -1,5 +1,6 @@
-
+
diff --git a/src/components/flights/FlightDetailSkeleton.vue b/src/components/flights/FlightDetailSkeleton.vue
index 7071716..b486bae 100644
--- a/src/components/flights/FlightDetailSkeleton.vue
+++ b/src/components/flights/FlightDetailSkeleton.vue
@@ -26,7 +26,7 @@ import BasicTextSkeleton from "components/basicSkeletons/BasicTextSkeleton.vue";
-
+
diff --git a/src/components/flights/FlightsListSkeleton.vue b/src/components/flights/FlightsListSkeleton.vue
index 0d4a220..ae68983 100644
--- a/src/components/flights/FlightsListSkeleton.vue
+++ b/src/components/flights/FlightsListSkeleton.vue
@@ -4,7 +4,7 @@ import {randomInt} from "src/composables/random";
-
+
@@ -12,7 +12,6 @@ import {randomInt} from "src/composables/random";
-
diff --git a/src/components/photos/PhotoGallery.vue b/src/components/photos/PhotoGallery.vue
index b2c35e7..1f27a27 100644
--- a/src/components/photos/PhotoGallery.vue
+++ b/src/components/photos/PhotoGallery.vue
@@ -1,14 +1,10 @@
diff --git a/src/pages/profile/flight/PhotoEditorPage.vue b/src/pages/profile/flight/PhotoEditorPage.vue
index f65df63..6d23f17 100644
--- a/src/pages/profile/flight/PhotoEditorPage.vue
+++ b/src/pages/profile/flight/PhotoEditorPage.vue
@@ -43,7 +43,7 @@ watch(() => [adjustment.value, croppedAreaForPreview.value], debounce(() => {
const savePhotoAdjustment = (photoId) => {
const adjustmentInput = {...adjustment.value};
- if (croppedArea.value.left !== null) {
+ if (croppedArea.value && croppedArea.value.left !== null) {
adjustmentInput.crop = {...croppedArea.value}
}
diff --git a/src/pages/public/TestPage.vue b/src/pages/public/TestPage.vue
new file mode 100644
index 0000000..22005a6
--- /dev/null
+++ b/src/pages/public/TestPage.vue
@@ -0,0 +1,15 @@
+
+
+
+
+ letadlo: {{ aircrafts }}
+
diff --git a/src/router/index.js b/src/router/index.js
index 4fd1751..ad062aa 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -3,16 +3,7 @@ import {createRouter, createMemoryHistory, createWebHistory, createWebHashHistor
import routes from './routes'
import {useAppStore} from "stores/App";
-/*
- * If not building with SSR mode, you can
- * directly export the Router instantiation;
- *
- * The function below can be async too; either use
- * async/await or return a Promise which resolves
- * with the Router instance.
- */
-
-export default route(function (/* { store, ssrContext } */) {
+export default route(function ({ store }) {
const createHistory = process.env.SERVER
? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory)
@@ -29,7 +20,7 @@ export default route(function (/* { store, ssrContext } */) {
Router.afterEach(async (to, from) => {
if (from.name !== to.name) {
- useAppStore().resetAfterRouteChange();
+ useAppStore(store).resetAfterRouteChange();
}
});
diff --git a/src/router/routes.js b/src/router/routes.js
index 63ac779..d979a9b 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -1,6 +1,7 @@
import PlainLayout from "layouts/PlainLayout.vue";
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: '/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}},
@@ -68,10 +69,10 @@ const routes = [
// Always leave this as last one,
// but you can also remove it
- {
- path: '/:catchAll(.*)*',
- component: () => import('pages/ErrorNotFound.vue')
- }
+ // {
+ // path: '/:catchAll(.*)*',
+ // component: () => import('pages/ErrorNotFound.vue')
+ // }
]
export default routes