Client - use <script setup> in components

This commit is contained in:
Sam 2021-11-10 21:19:27 +01:00
parent 857c0ecd2d
commit 1bede62d80
126 changed files with 2133 additions and 3207 deletions

View File

@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"><link rel="stylesheet" href="/static/css/leaflet.css"><title>FitTrackee</title><link href="/static/css/admin.2e1912ed.css" rel="prefetch"><link href="/static/css/main.0baa26a6.css" rel="prefetch"><link href="/static/css/main~workouts.2563ccfd.css" rel="prefetch"><link href="/static/css/profile.14a2947f.css" rel="prefetch"><link href="/static/css/reset.3e6931c7.css" rel="prefetch"><link href="/static/css/workouts.d952f3cf.css" rel="prefetch"><link href="/static/js/admin.4047df15.js" rel="prefetch"><link href="/static/js/chunk-2d0c9189.c81458cc.js" rel="prefetch"><link href="/static/js/chunk-2d0cf391.020c75ea.js" rel="prefetch"><link href="/static/js/chunk-2d0da8f3.c8c3e7e8.js" rel="prefetch"><link href="/static/js/chunk-2d2248b6.d84473c1.js" rel="prefetch"><link href="/static/js/chunk-2d22523a.4b710d99.js" rel="prefetch"><link href="/static/js/main.265d6693.js" rel="prefetch"><link href="/static/js/main~workouts.aa540c70.js" rel="prefetch"><link href="/static/js/profile.7e87449f.js" rel="prefetch"><link href="/static/js/reset.98679f6c.js" rel="prefetch"><link href="/static/js/workouts.52ba33b8.js" rel="prefetch"><link href="/static/css/app.88c1cb13.css" rel="preload" as="style"><link href="/static/js/app.e01bf3f7.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.5928fb7f.js" rel="preload" as="script"><link href="/static/css/app.88c1cb13.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.5928fb7f.js"></script><script src="/static/js/app.e01bf3f7.js"></script></body></html> <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"><link rel="stylesheet" href="/static/css/leaflet.css"><title>FitTrackee</title><link href="/static/css/admin.e96a4210.css" rel="prefetch"><link href="/static/css/main.2ee3c9ac.css" rel="prefetch"><link href="/static/css/main~workouts.ca97ac72.css" rel="prefetch"><link href="/static/css/profile.b792256b.css" rel="prefetch"><link href="/static/css/reset.dc50c1a7.css" rel="prefetch"><link href="/static/css/workouts.9c79c3ad.css" rel="prefetch"><link href="/static/js/admin.2f1d393d.js" rel="prefetch"><link href="/static/js/chunk-2d0c9189.c81458cc.js" rel="prefetch"><link href="/static/js/chunk-2d0cf391.020c75ea.js" rel="prefetch"><link href="/static/js/chunk-2d0da8f3.c8c3e7e8.js" rel="prefetch"><link href="/static/js/chunk-2d2248b6.d84473c1.js" rel="prefetch"><link href="/static/js/chunk-2d22523a.4b710d99.js" rel="prefetch"><link href="/static/js/main.c3f36893.js" rel="prefetch"><link href="/static/js/main~workouts.a74990d7.js" rel="prefetch"><link href="/static/js/profile.6a786c1d.js" rel="prefetch"><link href="/static/js/reset.6f6516bc.js" rel="prefetch"><link href="/static/js/workouts.ed2b92d6.js" rel="prefetch"><link href="/static/css/app.1b990916.css" rel="preload" as="style"><link href="/static/js/app.47532afb.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.71654064.js" rel="preload" as="script"><link href="/static/css/app.1b990916.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.71654064.js"></script><script src="/static/js/app.47532afb.js"></script></body></html>

View File

@ -64,7 +64,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/img/workouts/mountains.svg" "url": "/img/workouts/mountains.svg"
}, },
{ {
"revision": "ac27cacdce535196c127b4f950b04fcf", "revision": "2d61b8f556cfd6b2fd8cb6bb5d04b8e3",
"url": "/index.html" "url": "/index.html"
}, },
{ {
@ -76,12 +76,12 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/robots.txt" "url": "/robots.txt"
}, },
{ {
"revision": "2a55f1c804bfc2c51c03", "revision": "a1d50ec70acd25b1b70a",
"url": "/static/css/admin.2e1912ed.css" "url": "/static/css/admin.e96a4210.css"
}, },
{ {
"revision": "45aeb8689961e0a5de78", "revision": "abfdfe6ff6e351d4cef8",
"url": "/static/css/app.88c1cb13.css" "url": "/static/css/app.1b990916.css"
}, },
{ {
"revision": "82c1118c918377daaa71a320ab8eea42", "revision": "82c1118c918377daaa71a320ab8eea42",
@ -92,24 +92,24 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/css/leaflet.css" "url": "/static/css/leaflet.css"
}, },
{ {
"revision": "4f21a8566a32a3eaa6cc", "revision": "2afbe9449a719e10f79a",
"url": "/static/css/main.0baa26a6.css" "url": "/static/css/main.2ee3c9ac.css"
}, },
{ {
"revision": "44ec9db1f0dd0bd662ad", "revision": "d37b7cc3f4be1d095e98",
"url": "/static/css/main~workouts.2563ccfd.css" "url": "/static/css/main~workouts.ca97ac72.css"
}, },
{ {
"revision": "d13a074cdc41830448c5", "revision": "46a831b0d7a94cbae773",
"url": "/static/css/profile.14a2947f.css" "url": "/static/css/profile.b792256b.css"
}, },
{ {
"revision": "eaff42a53248eac38103", "revision": "9ef2fa45c16d6788a6e2",
"url": "/static/css/reset.3e6931c7.css" "url": "/static/css/reset.dc50c1a7.css"
}, },
{ {
"revision": "c3c3a3c7444bd3448bda", "revision": "8a364ff438b4214d5d65",
"url": "/static/css/workouts.d952f3cf.css" "url": "/static/css/workouts.9c79c3ad.css"
}, },
{ {
"revision": "e719f9244c69e28e7d00e725ca1e280e", "revision": "e719f9244c69e28e7d00e725ca1e280e",
@ -192,12 +192,12 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/img/pt-sans-v9-latin-regular.f1f73e45.svg" "url": "/static/img/pt-sans-v9-latin-regular.f1f73e45.svg"
}, },
{ {
"revision": "2a55f1c804bfc2c51c03", "revision": "a1d50ec70acd25b1b70a",
"url": "/static/js/admin.4047df15.js" "url": "/static/js/admin.2f1d393d.js"
}, },
{ {
"revision": "45aeb8689961e0a5de78", "revision": "abfdfe6ff6e351d4cef8",
"url": "/static/js/app.e01bf3f7.js" "url": "/static/js/app.47532afb.js"
}, },
{ {
"revision": "bd7d183c9f68e5f4027d", "revision": "bd7d183c9f68e5f4027d",
@ -220,27 +220,27 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/js/chunk-2d22523a.4b710d99.js" "url": "/static/js/chunk-2d22523a.4b710d99.js"
}, },
{ {
"revision": "639fd68690c8714c6f5c", "revision": "1631aa1204c2ef00fa57",
"url": "/static/js/chunk-vendors.5928fb7f.js" "url": "/static/js/chunk-vendors.71654064.js"
}, },
{ {
"revision": "4f21a8566a32a3eaa6cc", "revision": "2afbe9449a719e10f79a",
"url": "/static/js/main.265d6693.js" "url": "/static/js/main.c3f36893.js"
}, },
{ {
"revision": "44ec9db1f0dd0bd662ad", "revision": "d37b7cc3f4be1d095e98",
"url": "/static/js/main~workouts.aa540c70.js" "url": "/static/js/main~workouts.a74990d7.js"
}, },
{ {
"revision": "d13a074cdc41830448c5", "revision": "46a831b0d7a94cbae773",
"url": "/static/js/profile.7e87449f.js" "url": "/static/js/profile.6a786c1d.js"
}, },
{ {
"revision": "eaff42a53248eac38103", "revision": "9ef2fa45c16d6788a6e2",
"url": "/static/js/reset.98679f6c.js" "url": "/static/js/reset.6f6516bc.js"
}, },
{ {
"revision": "c3c3a3c7444bd3448bda", "revision": "8a364ff438b4214d5d65",
"url": "/static/js/workouts.52ba33b8.js" "url": "/static/js/workouts.ed2b92d6.js"
} }
]); ]);

View File

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts( importScripts(
"/precache-manifest.4d775da2b435165a0140d391654cdbc7.js" "/precache-manifest.2c04e97988213eb1aa1ce038eb4a9e1d.js"
); );
workbox.core.setCacheNameDetails({prefix: "fittrackee_client"}); workbox.core.setCacheNameDetails({prefix: "fittrackee_client"});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["admin"],{3409:function(e,t,n){"use strict";n("ca8b")},"4d13":function(e,t,n){"use strict";n("4fbb")},"4fbb":function(e,t,n){},"89b3":function(e,t,n){"use strict";n.r(t);var c=n("7a23"),o=n("f7f9"),r=n("dad5"),u=n("2906"),a=function(e){return Object(c["pushScopeId"])("data-v-21f8956c"),e=e(),Object(c["popScopeId"])(),e},b={id:"admin",class:"view"},i={key:0,class:"container"},s=a((function(){return Object(c["createElementVNode"])("div",{id:"bottom"},null,-1)})),O=Object(c["defineComponent"])({setup:function(e){var t=Object(u["a"])(),n=Object(c["computed"])((function(){return t.getters[r["b"].GETTERS.APP_CONFIG]})),a=Object(c["computed"])((function(){return t.getters[r["b"].GETTERS.APP_STATS]})),O=Object(c["computed"])((function(){return t.getters[r["a"].GETTERS.IS_ADMIN]})),l=Object(c["computed"])((function(){return t.getters[r["a"].GETTERS.USER_LOADING]}));return Object(c["onBeforeMount"])((function(){return t.dispatch(r["b"].ACTIONS.GET_APPLICATION_STATS)})),function(e,t){var r=Object(c["resolveComponent"])("router-view");return Object(c["openBlock"])(),Object(c["createElementBlock"])("div",b,[Object(c["unref"])(l)?Object(c["createCommentVNode"])("",!0):(Object(c["openBlock"])(),Object(c["createElementBlock"])("div",i,[Object(c["unref"])(O)?(Object(c["openBlock"])(),Object(c["createBlock"])(r,{key:0,appConfig:Object(c["unref"])(n),appStatistics:Object(c["unref"])(a)},null,8,["appConfig","appStatistics"])):(Object(c["openBlock"])(),Object(c["createBlock"])(o["a"],{key:1})),s]))])}}}),l=(n("4d13"),n("6b0d")),p=n.n(l);const f=p()(O,[["__scopeId","data-v-21f8956c"]]);t["default"]=f},ca8b:function(e,t,n){},f7f9:function(e,t,n){"use strict";var c=n("7a23"),o={id:"error"},r={class:"error-content"},u=Object(c["defineComponent"])({props:{title:null,message:null,buttonText:null,path:{default:"/"}},setup:function(e){var t=e,n=Object(c["toRefs"])(t),u=n.buttonText,a=n.title,b=n.message,i=n.path;return function(e,t){return Object(c["openBlock"])(),Object(c["createElementBlock"])("div",o,[Object(c["createElementVNode"])("div",r,[Object(c["createElementVNode"])("h1",null,Object(c["toDisplayString"])(Object(c["unref"])(a)),1),Object(c["createElementVNode"])("p",null,Object(c["toDisplayString"])(Object(c["unref"])(b)),1),Object(c["unref"])(u)?(Object(c["openBlock"])(),Object(c["createElementBlock"])("button",{key:0,onClick:t[0]||(t[0]=function(t){return e.$router.push(Object(c["unref"])(i))}),class:"upper"},Object(c["toDisplayString"])(Object(c["unref"])(u)),1)):Object(c["createCommentVNode"])("",!0)])])}}}),a=(n("3409"),n("6b0d")),b=n.n(a);const i=b()(u,[["__scopeId","data-v-79ec50fd"]]);var s=i,O=Object(c["defineComponent"])({props:{target:{default:"PAGE"}},setup:function(e){var t=e,n=Object(c["toRefs"])(t),o=n.target;return function(e,t){return Object(c["openBlock"])(),Object(c["createBlock"])(s,{title:"404",message:e.$t("error.NOT_FOUND.".concat(Object(c["unref"])(o))),"button-text":e.$t("common.HOME")},null,8,["message","button-text"])}}});const l=O;t["a"]=l}}]);
//# sourceMappingURL=admin.2f1d393d.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["admin"],{"726e":function(e,t,n){},8185:function(e,t,n){"use strict";n("a27b")},"89b3":function(e,t,n){"use strict";n.r(t);var o=n("7a23"),c=function(e){return Object(o["pushScopeId"])("data-v-2fbe41f1"),e=e(),Object(o["popScopeId"])(),e},r={id:"admin",class:"view"},a={key:0,class:"container"},i=c((function(){return Object(o["createElementVNode"])("div",{id:"bottom"},null,-1)}));function u(e,t,n,c,u,p){var s=Object(o["resolveComponent"])("router-view"),b=Object(o["resolveComponent"])("NotFound");return Object(o["openBlock"])(),Object(o["createElementBlock"])("div",r,[e.userLoading?Object(o["createCommentVNode"])("",!0):(Object(o["openBlock"])(),Object(o["createElementBlock"])("div",a,[e.isAuthUserAmin?(Object(o["openBlock"])(),Object(o["createBlock"])(s,{key:0,appConfig:e.appConfig,appStatistics:e.appStatistics},null,8,["appConfig","appStatistics"])):(Object(o["openBlock"])(),Object(o["createBlock"])(b,{key:1})),i]))])}var p=n("f7f9"),s=n("dad5"),b=n("2906"),d=Object(o["defineComponent"])({name:"Admin",components:{NotFound:p["a"]},setup:function(){var e=Object(b["a"])();Object(o["onBeforeMount"])((function(){return e.dispatch(s["b"].ACTIONS.GET_APPLICATION_STATS)}));var t=Object(o["computed"])((function(){return e.getters[s["b"].GETTERS.APP_LOADING]})),n=Object(o["computed"])((function(){return e.getters[s["b"].GETTERS.APP_CONFIG]})),c=Object(o["computed"])((function(){return e.getters[s["b"].GETTERS.APP_STATS]})),r=Object(o["computed"])((function(){return e.getters[s["a"].GETTERS.IS_ADMIN]})),a=Object(o["computed"])((function(){return e.getters[s["a"].GETTERS.USER_LOADING]}));return{appConfig:n,appLoading:t,appStatistics:c,isAuthUserAmin:r,userLoading:a}}}),l=(n("d14e"),n("6b0d")),O=n.n(l);const m=O()(d,[["render",u],["__scopeId","data-v-2fbe41f1"]]);t["default"]=m},a27b:function(e,t,n){},d14e:function(e,t,n){"use strict";n("726e")},f7f9:function(e,t,n){"use strict";var o=n("7a23");function c(e,t,n,c,r,a){var i=Object(o["resolveComponent"])("Error");return Object(o["openBlock"])(),Object(o["createBlock"])(i,{title:"404",message:e.$t("error.NOT_FOUND.".concat(e.target)),"button-text":e.$t("common.HOME")},null,8,["message","button-text"])}var r={id:"error"},a={class:"error-content"};function i(e,t,n,c,i,u){return Object(o["openBlock"])(),Object(o["createElementBlock"])("div",r,[Object(o["createElementVNode"])("div",a,[Object(o["createElementVNode"])("h1",null,Object(o["toDisplayString"])(e.title),1),Object(o["createElementVNode"])("p",null,Object(o["toDisplayString"])(e.message),1),e.buttonText?(Object(o["openBlock"])(),Object(o["createElementBlock"])("button",{key:0,onClick:t[0]||(t[0]=function(t){return e.$router.push(e.path)}),class:"upper"},Object(o["toDisplayString"])(e.buttonText),1)):Object(o["createCommentVNode"])("",!0)])])}var u=Object(o["defineComponent"])({name:"Error",props:{title:{type:String,required:!0},message:{type:String},buttonText:{type:String},path:{type:String,default:"/"}}}),p=(n("8185"),n("6b0d")),s=n.n(p);const b=s()(u,[["render",i],["__scopeId","data-v-58e20d75"]]);var d=b,l=Object(o["defineComponent"])({name:"NotFound",components:{Error:d},props:{target:{type:String,default:"PAGE"}}});const O=s()(l,[["render",c]]);t["a"]=O}}]);
//# sourceMappingURL=admin.4047df15.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["profile"],{"36e8":function(e,t,n){"use strict";n.r(t);var c=n("7a23"),r=n("dad5"),o=n("2906"),u={key:0,id:"profile",class:"container view"},a=Object(c["defineComponent"])({setup:function(e){var t=Object(o["a"])(),n=Object(c["computed"])((function(){return t.getters[r["a"].GETTERS.AUTH_USER_PROFILE]}));return function(e,t){var r=Object(c["resolveComponent"])("router-view");return Object(c["unref"])(n).username?(Object(c["openBlock"])(),Object(c["createElementBlock"])("div",u,[Object(c["createVNode"])(r,{user:Object(c["unref"])(n)},null,8,["user"])])):Object(c["createCommentVNode"])("",!0)}}}),s=(n("44ab"),n("6b0d")),b=n.n(s);const i=b()(a,[["__scopeId","data-v-bb090bfa"]]);t["default"]=i},"44ab":function(e,t,n){"use strict";n("4fe6")},"4fe6":function(e,t,n){},"9b98":function(e,t,n){"use strict";n("d332")},ad3d:function(e,t,n){"use strict";n.r(t);var c=n("7a23"),r=n("6c02"),o=n("3c44"),u=n("71a7"),a=n("dad5"),s=n("2906"),b={key:0,id:"user",class:"view"},i={class:"box"},d=Object(c["defineComponent"])({setup:function(e){var t=Object(r["c"])(),n=Object(s["a"])(),d=Object(c["computed"])((function(){return n.getters[a["e"].GETTERS.USER]}));return Object(c["onBeforeMount"])((function(){t.params.username&&"string"===typeof t.params.username&&n.dispatch(a["e"].ACTIONS.GET_USER,t.params.username)})),Object(c["onBeforeUnmount"])((function(){n.dispatch(a["e"].ACTIONS.EMPTY_USER)})),function(e,t){return Object(c["unref"])(d).username?(Object(c["openBlock"])(),Object(c["createElementBlock"])("div",b,[Object(c["createVNode"])(o["a"],{user:Object(c["unref"])(d)},null,8,["user"]),Object(c["createElementVNode"])("div",i,[Object(c["createVNode"])(u["a"],{user:Object(c["unref"])(d),"from-admin":!0},null,8,["user"])])])):Object(c["createCommentVNode"])("",!0)}}}),f=(n("9b98"),n("6b0d")),O=n.n(f);const j=O()(d,[["__scopeId","data-v-218f8f1e"]]);t["default"]=j},d332:function(e,t,n){}}]);
//# sourceMappingURL=profile.6a786c1d.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["profile"],{"0ab6":function(e,t,n){},"36e8":function(e,t,n){"use strict";n.r(t);var r=n("7a23"),c={key:0,id:"profile",class:"container view"};function o(e,t,n,o,a,s){var u=Object(r["resolveComponent"])("router-view");return e.authUser.username?(Object(r["openBlock"])(),Object(r["createElementBlock"])("div",c,[Object(r["createVNode"])(u,{user:e.authUser},null,8,["user"])])):Object(r["createCommentVNode"])("",!0)}var a=n("dad5"),s=n("2906"),u=Object(r["defineComponent"])({name:"ProfileView",setup:function(){var e=Object(s["a"])(),t=Object(r["computed"])((function(){return e.getters[a["a"].GETTERS.AUTH_USER_PROFILE]}));return{authUser:t}}}),i=(n("a6f2"),n("6b0d")),d=n.n(i);const f=d()(u,[["render",o],["__scopeId","data-v-37d55f74"]]);t["default"]=f},"7ffc":function(e,t,n){"use strict";n("b288")},a6f2:function(e,t,n){"use strict";n("0ab6")},ad3d:function(e,t,n){"use strict";n.r(t);var r=n("7a23"),c={key:0,id:"user",class:"view"},o={class:"box"};function a(e,t,n,a,s,u){var i=Object(r["resolveComponent"])("UserHeader"),d=Object(r["resolveComponent"])("UserInfos");return e.user.username?(Object(r["openBlock"])(),Object(r["createElementBlock"])("div",c,[Object(r["createVNode"])(i,{user:e.user},null,8,["user"]),Object(r["createElementVNode"])("div",o,[Object(r["createVNode"])(d,{user:e.user,"from-admin":!0},null,8,["user"])])])):Object(r["createCommentVNode"])("",!0)}var s=n("6c02"),u=n("3c44"),i=n("71a7"),d=n("dad5"),f=n("2906"),b=Object(r["defineComponent"])({name:"UserView",components:{UserHeader:u["a"],UserInfos:i["a"]},setup:function(){var e=Object(s["c"])(),t=Object(f["a"])(),n=Object(r["computed"])((function(){return t.getters[d["e"].GETTERS.USER]}));return Object(r["onBeforeMount"])((function(){e.params.username&&"string"===typeof e.params.username&&t.dispatch(d["e"].ACTIONS.GET_USER,e.params.username)})),Object(r["onBeforeUnmount"])((function(){t.dispatch(d["e"].ACTIONS.EMPTY_USER)})),{user:n}}}),m=(n("7ffc"),n("6b0d")),p=n.n(m);const O=p()(b,[["render",a],["__scopeId","data-v-82f4bbf6"]]);t["default"]=O},b288:function(e,t,n){}}]);
//# sourceMappingURL=profile.7e87449f.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -70,6 +70,12 @@
"plugin:import/recommended", "plugin:import/recommended",
"plugin:import/typescript" "plugin:import/typescript"
], ],
"globals": {
"defineProps": "readonly",
"defineEmits": "readonly",
"defineExpose": "readonly",
"withDefaults": "readonly"
},
"settings": { "settings": {
"import/resolver": "typescript" "import/resolver": "typescript"
}, },

View File

@ -22,15 +22,8 @@
<Footer v-if="appConfig" :version="appConfig ? appConfig.version : ''" /> <Footer v-if="appConfig" :version="appConfig ? appConfig.version : ''" />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import { ComputedRef, computed, ref, onBeforeMount, onMounted } from 'vue'
ComputedRef,
computed,
defineComponent,
ref,
onBeforeMount,
onMounted,
} from 'vue'
import Footer from '@/components/Footer.vue' import Footer from '@/components/Footer.vue'
import NavBar from '@/components/NavBar.vue' import NavBar from '@/components/NavBar.vue'
@ -39,67 +32,44 @@
import { TAppConfig } from '@/types/application' import { TAppConfig } from '@/types/application'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
export default defineComponent({ const store = useStore()
name: 'App',
components: {
Footer,
NavBar,
NoConfig,
},
setup() {
const store = useStore()
const appConfig: ComputedRef<TAppConfig> = computed( const appConfig: ComputedRef<TAppConfig> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG] () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
) )
const appLoading: ComputedRef<boolean> = computed( const appLoading: ComputedRef<boolean> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_LOADING] () => store.getters[ROOT_STORE.GETTERS.APP_LOADING]
) )
const hideScrollBar = ref(false) const hideScrollBar = ref(false)
const displayScrollButton = ref(false) const displayScrollButton = ref(false)
onBeforeMount(() => onBeforeMount(() => store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG))
store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG) onMounted(() => scroll())
)
onMounted(() => scroll())
function updateHideScrollBar(isMenuOpen: boolean) { function updateHideScrollBar(isMenuOpen: boolean) {
hideScrollBar.value = isMenuOpen hideScrollBar.value = isMenuOpen
} }
function isScrolledToBottom(element: Element): boolean {
function isScrolledToBottom(element: Element): boolean { return (
return ( element.getBoundingClientRect().top < window.innerHeight &&
element.getBoundingClientRect().top < window.innerHeight && element.getBoundingClientRect().bottom >= 0
element.getBoundingClientRect().bottom >= 0 )
) }
} function scroll() {
function scroll() { window.onscroll = () => {
window.onscroll = () => { let bottom = document.querySelector('#bottom')
let bottom = document.querySelector('#bottom') displayScrollButton.value = bottom !== null && isScrolledToBottom(bottom)
displayScrollButton.value = }
bottom !== null && isScrolledToBottom(bottom) }
} function scrollToTop() {
} window.scrollTo({
function scrollToTop() { top: 0,
window.scrollTo({ behavior: 'smooth',
top: 0, })
behavior: 'smooth', setTimeout(() => {
}) displayScrollButton.value = false
setTimeout(() => { }, 300)
displayScrollButton.value = false }
}, 300)
}
return {
appConfig,
appLoading,
hideScrollBar,
displayScrollButton,
scrollToTop,
updateHideScrollBar,
}
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -82,13 +82,12 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import {
ComputedRef, ComputedRef,
PropType,
computed, computed,
defineComponent,
reactive, reactive,
withDefaults,
onBeforeMount, onBeforeMount,
} from 'vue' } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -98,64 +97,55 @@
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getFileSizeInMB } from '@/utils/files' import { getFileSizeInMB } from '@/utils/files'
export default defineComponent({ interface Props {
name: 'AdminApplication', appConfig: TAppConfig
props: { edition?: boolean
appConfig: { }
type: Object as PropType<TAppConfig>, const props = withDefaults(defineProps<Props>(), {
required: true, edition: false,
},
edition: {
type: Boolean,
default: false,
},
},
setup(props) {
const store = useStore()
const router = useRouter()
const appData: TAppConfigForm = reactive({
max_users: 0,
max_single_file_size: 0,
max_zip_file_size: 0,
gpx_limit_import: 0,
})
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
onBeforeMount(() => {
if (props.appConfig) {
updateForm(props.appConfig)
}
})
function updateForm(appConfig: TAppConfig) {
Object.keys(appData).map((key) => {
;['max_single_file_size', 'max_zip_file_size'].includes(key)
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(appData[key] = getFileSizeInMB(appConfig[key]))
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(appData[key] = appConfig[key])
})
}
function onCancel() {
updateForm(props.appConfig)
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
router.push('/admin/application')
}
function onSubmit() {
const formData: TAppConfigForm = Object.assign({}, appData)
formData.max_single_file_size *= 1048576
formData.max_zip_file_size *= 1048576
store.dispatch(ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG, formData)
}
return { appData, errorMessages, onCancel, onSubmit }
},
}) })
const store = useStore()
const router = useRouter()
const appData: TAppConfigForm = reactive({
max_users: 0,
max_single_file_size: 0,
max_zip_file_size: 0,
gpx_limit_import: 0,
})
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
onBeforeMount(() => {
if (props.appConfig) {
updateForm(props.appConfig)
}
})
function updateForm(appConfig: TAppConfig) {
Object.keys(appData).map((key) => {
;['max_single_file_size', 'max_zip_file_size'].includes(key)
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(appData[key] = getFileSizeInMB(appConfig[key]))
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(appData[key] = appConfig[key])
})
}
function onCancel() {
updateForm(props.appConfig)
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
router.push('/admin/application')
}
function onSubmit() {
const formData: TAppConfigForm = Object.assign({}, appData)
formData.max_single_file_size *= 1048576
formData.max_zip_file_size *= 1048576
store.dispatch(ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG, formData)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -3,7 +3,7 @@
<Card> <Card>
<template #title>{{ $t('admin.ADMINISTRATION') }}</template> <template #title>{{ $t('admin.ADMINISTRATION') }}</template>
<template #content> <template #content>
<AppStatsCards :app-statistics="appStatistics" /> <AppStatsCards :appStatistics="appStatistics" />
<div class="admin-menu description-list"> <div class="admin-menu description-list">
<dl> <dl>
<dt> <dt>
@ -46,32 +46,22 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, capitalize, defineComponent } from 'vue' import { capitalize, toRefs, withDefaults } from 'vue'
import AppStatsCards from '@/components/Administration/AppStatsCards.vue' import AppStatsCards from '@/components/Administration/AppStatsCards.vue'
import Card from '@/components/Common/Card.vue' import Card from '@/components/Common/Card.vue'
import { IAppStatistics, TAppConfig } from '@/types/application' import { IAppStatistics, TAppConfig } from '@/types/application'
export default defineComponent({ interface Props {
name: 'AdminMenu', appConfig: TAppConfig
components: { appStatistics?: IAppStatistics
AppStatsCards, }
Card, const props = withDefaults(defineProps<Props>(), {
}, appStatistics: () => ({} as IAppStatistics),
props: {
appConfig: {
type: Object as PropType<TAppConfig>,
required: true,
},
appStatistics: {
type: Object as PropType<IAppStatistics>,
},
},
setup() {
return { capitalize }
},
}) })
const { appConfig, appStatistics } = toRefs(props)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -82,8 +82,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ComputedRef, computed, defineComponent } from 'vue' import { ComputedRef, computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ROOT_STORE, SPORTS_STORE } from '@/store/constants' import { ROOT_STORE, SPORTS_STORE } from '@/store/constants'
@ -91,28 +91,22 @@
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { translateSports } from '@/utils/sports' import { translateSports } from '@/utils/sports'
export default defineComponent({ const { t } = useI18n()
name: 'AdminSports', const store = useStore()
setup() {
const { t } = useI18n()
const store = useStore()
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(store.getters[SPORTS_STORE.GETTERS.SPORTS], t)
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
function updateSportStatus(id: number, isActive: boolean) { const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
store.dispatch(SPORTS_STORE.ACTIONS.UPDATE_SPORTS, { translateSports(store.getters[SPORTS_STORE.GETTERS.SPORTS], t)
id, )
isActive, const errorMessages: ComputedRef<string | string[] | null> = computed(
}) () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
} )
return { errorMessages, translatedSports, updateSportStatus } function updateSportStatus(id: number, isActive: boolean) {
}, store.dispatch(SPORTS_STORE.ACTIONS.UPDATE_SPORTS, {
}) id,
isActive,
})
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -115,12 +115,11 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { format } from 'date-fns' import { format } from 'date-fns'
import { import {
ComputedRef, ComputedRef,
computed, computed,
defineComponent,
reactive, reactive,
watch, watch,
capitalize, capitalize,
@ -139,89 +138,63 @@
import { getQuery, sortList } from '@/utils/api' import { getQuery, sortList } from '@/utils/api'
import { getDateWithTZ } from '@/utils/dates' import { getDateWithTZ } from '@/utils/dates'
export default defineComponent({ const store = useStore()
name: 'AdminUsers', const route = useRoute()
components: { const router = useRouter()
FilterSelects,
Pagination,
UserPicture,
},
setup() {
const store = useStore()
const route = useRoute()
const router = useRouter()
const orderByList: string[] = [ const orderByList: string[] = [
'admin', 'admin',
'created_at', 'created_at',
'username', 'username',
'workouts_count', 'workouts_count',
] ]
const defaultOrderBy = 'created_at' const defaultOrderBy = 'created_at'
let query: TPaginationPayload = reactive( let query: TPaginationPayload = reactive(
getQuery(route.query, orderByList, defaultOrderBy) getQuery(route.query, orderByList, defaultOrderBy)
) )
const authUser: ComputedRef<IUserProfile> = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
)
const users: ComputedRef<IUserProfile[]> = computed(
() => store.getters[USERS_STORE.GETTERS.USERS]
)
const pagination: ComputedRef<IPagination> = computed(
() => store.getters[USERS_STORE.GETTERS.USERS_PAGINATION]
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
const authUser: ComputedRef<IUserProfile> = computed( onBeforeMount(() => loadUsers(query))
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
)
const users: ComputedRef<IUserProfile[]> = computed(
() => store.getters[USERS_STORE.GETTERS.USERS]
)
const pagination: ComputedRef<IPagination> = computed(
() => store.getters[USERS_STORE.GETTERS.USERS_PAGINATION]
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
function loadUsers(queryParams: TPaginationPayload) { function loadUsers(queryParams: TPaginationPayload) {
store.dispatch(USERS_STORE.ACTIONS.GET_USERS, queryParams) store.dispatch(USERS_STORE.ACTIONS.GET_USERS, queryParams)
} }
function updateUser(username: string, admin: boolean) { function updateUser(username: string, admin: boolean) {
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, { store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
username, username,
admin, admin,
}) })
} }
function reloadUsers(queryParam: string, queryValue: string) { function reloadUsers(queryParam: string, queryValue: string) {
query[queryParam] = queryValue query[queryParam] = queryValue
if (queryParam === 'per_page') { if (queryParam === 'per_page') {
query.page = 1 query.page = 1
} }
router.push({ path: '/admin/users', query }) router.push({ path: '/admin/users', query })
} }
onBeforeMount(() => loadUsers(query)) onUnmounted(() => {
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USERS)
watch(
() => route.query,
(newQuery: LocationQuery) => {
query = getQuery(newQuery, orderByList, defaultOrderBy, { query })
loadUsers(query)
}
)
onUnmounted(() => {
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USERS)
})
return {
authUser,
errorMessages,
orderByList,
pagination,
query,
sortList,
users,
capitalize,
format,
getDateWithTZ,
reloadUsers,
updateUser,
}
},
}) })
watch(
() => route.query,
(newQuery: LocationQuery) => {
query = getQuery(newQuery, orderByList, defaultOrderBy, { query })
loadUsers(query)
}
)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -23,45 +23,34 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent, computed } from 'vue' import { computed, withDefaults } from 'vue'
import StatCard from '@/components/Common/StatCard.vue' import StatCard from '@/components/Common/StatCard.vue'
import { IAppStatistics } from '@/types/application' import { IAppStatistics } from '@/types/application'
import { getReadableFileSize } from '@/utils/files' import { getReadableFileSize } from '@/utils/files'
export default defineComponent({ interface Props {
name: 'UserStatsCards', appStatistics?: IAppStatistics
components: { }
StatCard, const props = withDefaults(defineProps<Props>(), {
}, appStatistics: () => ({} as IAppStatistics),
props: {
appStatistics: {
type: Object as PropType<IAppStatistics>,
default: () => {
return {}
},
},
},
setup(props) {
return {
uploadDirSize: computed(() =>
props.appStatistics.uploads_dir_size
? getReadableFileSize(props.appStatistics.uploads_dir_size, false)
: { size: 0, suffix: 'bytes' }
),
usersCount: computed(() =>
props.appStatistics.users ? props.appStatistics.users : 0
),
sportsCount: computed(() =>
props.appStatistics.sports ? props.appStatistics.sports : 0
),
workoutCount: computed(() =>
props.appStatistics.workouts ? props.appStatistics.workouts : 0
),
}
},
}) })
const uploadDirSize = computed(() =>
props.appStatistics.uploads_dir_size
? getReadableFileSize(props.appStatistics.uploads_dir_size, false)
: { size: 0, suffix: 'bytes' }
)
const usersCount = computed(() =>
props.appStatistics.users ? props.appStatistics.users : 0
)
const sportsCount = computed(() =>
props.appStatistics.sports ? props.appStatistics.sports : 0
)
const workoutCount = computed(() =>
props.appStatistics.workouts ? props.appStatistics.workouts : 0
)
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,19 +1,13 @@
<template> <template>
<div id="about"> <div id="bike">
<img class="bike-img" :src="'/img/bike.svg'" alt="mountain bike" /> <img class="bike-img" :src="'/img/bike.svg'" alt="mountain bike" />
</div> </div>
</template> </template>
<script>
export default {
name: 'About',
}
</script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '~@/scss/base'; @import '~@/scss/base';
#about { #bike {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -4,15 +4,12 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs } from 'vue'
const props = defineProps<{
export default defineComponent({ message: string
name: 'AlertMessage', }>()
props: { const { message } = toRefs(props)
message: String,
},
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -9,13 +9,6 @@
</div> </div>
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Card',
})
</script>
<style lang="scss"> <style lang="scss">
@import '~@/scss/base'; @import '~@/scss/base';

View File

@ -14,47 +14,35 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref, watch } from 'vue' import { ref, watch, withDefaults } from 'vue'
export default defineComponent({ interface Props {
name: 'CustomTextArea', name: string
props: { charLimit?: number
charLimit: { disabled?: boolean
type: Number, input?: string
default: 500, }
}, const props = withDefaults(defineProps<Props>(), {
disabled: { charLimit: 500,
type: Boolean, disabled: false,
default: false, input: '',
},
input: {
type: String,
default: '',
},
name: {
type: String,
required: true,
},
},
emits: ['updateValue'],
setup(props, { emit }) {
let text = ref('')
function updateText(event: Event & { target: HTMLInputElement }) {
emit('updateValue', event.target.value)
}
watch(
() => props.input,
(value) => {
text.value = value
}
)
return { text, updateText }
},
}) })
const emit = defineEmits(['updateValue'])
let text = ref('')
function updateText(event: Event & { target: HTMLInputElement }) {
emit('updateValue', event.target.value)
}
watch(
() => props.input,
(value) => {
text.value = value
}
)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -17,53 +17,37 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent, ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { IDropdownOption, TDropdownOptions } from '@/types/forms' import { IDropdownOption, TDropdownOptions } from '@/types/forms'
interface Props {
options: TDropdownOptions
selected: string
}
const props = defineProps<Props>()
export default defineComponent({ const emit = defineEmits({
name: 'Dropdown', selected: (option: IDropdownOption) => option,
props: {
options: {
type: Object as PropType<TDropdownOptions>,
required: true,
},
selected: {
type: String,
required: true,
},
},
emits: {
selected: (option: IDropdownOption) => option,
},
setup(props, { emit }) {
const route = useRoute()
let isOpen = ref(false)
let dropdownOptions = props.options.map((option) => option)
function toggleDropdown() {
isOpen.value = !isOpen.value
}
function updateSelected(option: IDropdownOption) {
emit('selected', option)
isOpen.value = false
}
watch(
() => route.path,
() => (isOpen.value = false)
)
return {
dropdownOptions,
isOpen,
toggleDropdown,
updateSelected,
}
},
}) })
const route = useRoute()
let isOpen = ref(false)
let dropdownOptions = props.options.map((option) => option)
function toggleDropdown() {
isOpen.value = !isOpen.value
}
function updateSelected(option: IDropdownOption) {
emit('selected', option)
isOpen.value = false
}
watch(
() => route.path,
() => (isOpen.value = false)
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -10,28 +10,19 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs, withDefaults } from 'vue'
export default defineComponent({ interface Props {
name: 'Error', title: string
props: { message: string
title: { buttonText: string
type: String, path?: string
required: true, }
}, const props = withDefaults(defineProps<Props>(), {
message: { path: '/',
type: String,
},
buttonText: {
type: String,
},
path: {
type: String,
default: '/',
},
},
}) })
const { buttonText, title, message, path } = toRefs(props)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -9,15 +9,14 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs } from 'vue'
export default defineComponent({ interface Props {
name: 'ErrorMessage', message: string | string[]
props: { }
message: [String, Array], const props = defineProps<Props>()
}, const { message } = toRefs(props)
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -34,7 +34,7 @@
:value="query.per_page" :value="query.per_page"
@change="onSelectUpdate" @change="onSelectUpdate"
> >
<option v-for="nb in per_page" :value="nb" :key="nb"> <option v-for="nb in perPage" :value="nb" :key="nb">
{{ nb }} {{ nb }}
</option> </option>
</select> </select>
@ -42,43 +42,27 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent } from 'vue' import { toRefs } from 'vue'
import { TPaginationPayload } from '@/types/api' import { TPaginationPayload } from '@/types/api'
export default defineComponent({ interface Props {
name: 'FilterSelects', order_by: string[]
props: { query: TPaginationPayload
order_by: { sort: string[]
type: Object as PropType<string[]>, message: string
required: true, }
}, const props = defineProps<Props>()
query: {
type: Object as PropType<TPaginationPayload>,
required: true,
},
sort: {
type: Object as PropType<string[]>,
required: true,
},
message: {
type: String,
required: true,
},
},
emits: ['updateSelect'],
setup(props, { emit }) {
function onSelectUpdate(event: Event & { target: HTMLInputElement }) {
emit('updateSelect', event.target.id, event.target.value)
}
return { const emit = defineEmits(['updateSelect'])
per_page: [10, 25, 50, 100],
onSelectUpdate, const { order_by, query, sort, message } = toRefs(props)
} const perPage = [10, 25, 50, 100]
},
}) function onSelectUpdate(event: Event & { target: HTMLInputElement }) {
emit('updateSelect', event.target.id, event.target.value)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -20,8 +20,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, inject } from 'vue' import { inject, toRefs, withDefaults } from 'vue'
import CyclingSport from '@/components/Common/Images/SportImage/CyclingSport.vue' import CyclingSport from '@/components/Common/Images/SportImage/CyclingSport.vue'
import CyclingTransport from '@/components/Common/Images/SportImage/CyclingTransport.vue' import CyclingTransport from '@/components/Common/Images/SportImage/CyclingTransport.vue'
@ -35,33 +35,14 @@
import Trail from '@/components/Common/Images/SportImage/Trail.vue' import Trail from '@/components/Common/Images/SportImage/Trail.vue'
import Walking from '@/components/Common/Images/SportImage/Walking.vue' import Walking from '@/components/Common/Images/SportImage/Walking.vue'
export default defineComponent({ interface Props {
name: 'SportImage', sportLabel: string
components: { title?: string
CyclingSport, }
CyclingTransport, const props = withDefaults(defineProps<Props>(), {
Hiking, title: '',
MountainBiking,
MountainBikingElectric,
Rowing,
Running,
SkiingAlpine,
SkiingCrossCountry,
Trail,
Walking,
},
props: {
sportLabel: {
type: String,
required: true,
},
title: {
type: String,
required: false,
},
},
setup() {
return { sportColors: inject('sportColors') }
},
}) })
const { sportLabel, title } = toRefs(props)
const sportColors = inject('sportColors')
</script> </script>

View File

@ -2,13 +2,6 @@
<div class="loader" /> <div class="loader" />
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Loader',
})
</script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '~@/scss/base'; @import '~@/scss/base';
.loader { .loader {

View File

@ -31,38 +31,30 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ComputedRef, computed, defineComponent, onUnmounted } from 'vue' import { ComputedRef, computed, toRefs, withDefaults, onUnmounted } from 'vue'
import { ROOT_STORE } from '@/store/constants' import { ROOT_STORE } from '@/store/constants'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
export default defineComponent({ interface Props {
name: 'Modal', title: string
props: { message: string
title: { strongMessage?: string | null
type: String, }
required: true, const props = withDefaults(defineProps<Props>(), {
}, strongMessage: () => null,
message: {
type: String,
required: true,
},
strongMessage: {
type: String || null,
default: null,
},
},
emits: ['cancelAction', 'confirmAction'],
setup(props, { emit }) {
const store = useStore()
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
return { errorMessages, emit }
},
}) })
const emit = defineEmits(['cancelAction', 'confirmAction'])
const store = useStore()
const { title, message, strongMessage } = toRefs(props)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -6,21 +6,15 @@
/> />
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs, withDefaults } from 'vue'
import Error from '@/components/Common/Error.vue' import Error from '@/components/Common/Error.vue'
interface Props {
export default defineComponent({ target?: string
name: 'NotFound', }
components: { const props = withDefaults(defineProps<Props>(), {
Error, target: 'PAGE',
},
props: {
target: {
type: String,
default: 'PAGE',
},
},
}) })
const { target } = toRefs(props)
</script> </script>

View File

@ -42,37 +42,27 @@
</nav> </nav>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent } from 'vue' import { toRefs } from 'vue'
import { IPagination, TPaginationPayload } from '@/types/api' import { IPagination } from '@/types/api'
import { TWorkoutsPayload } from '@/types/workouts'
import { rangePagination } from '@/utils/api' import { rangePagination } from '@/utils/api'
export default defineComponent({ interface Props {
name: 'Pagination', pagination: IPagination
props: { path: string
pagination: { query: TWorkoutsPayload
type: Object as PropType<IPagination>, }
required: true, const props = defineProps<Props>()
},
path: { const { pagination, path, query } = toRefs(props)
type: String,
required: true, function getQuery(page: number, cursor?: number): TWorkoutsPayload {
}, const newQuery = Object.assign({}, query.value)
query: { newQuery.page = cursor ? page + cursor : page
type: Object as PropType<TPaginationPayload>, return newQuery
required: true, }
},
},
setup(props) {
function getQuery(page: number, cursor?: number): TPaginationPayload {
const newQuery = Object.assign({}, props.query)
newQuery.page = cursor ? page + cursor : page
return newQuery
}
return { rangePagination, getQuery }
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -12,26 +12,16 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs } from 'vue'
export default defineComponent({ interface Props {
name: 'StatCard', icon: string
props: { text: string
icon: { value: string | number
type: String, }
required: true, const props = defineProps<Props>()
}, const { icon, text, value } = toRefs(props)
value: {
type: [String, Number],
required: true,
},
text: {
type: String,
required: true,
},
},
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -22,29 +22,21 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType } from 'vue' import { toRefs, withDefaults } from 'vue'
import { IWorkout } from '@/types/workouts' import { IWorkout } from '@/types/workouts'
import { getApiUrl } from '@/utils' import { getApiUrl } from '@/utils'
export default defineComponent({ interface Props {
name: 'StaticMap', workout: IWorkout
props: { displayHover?: boolean
workout: { }
type: Object as PropType<IWorkout>, const props = withDefaults(defineProps<Props>(), {
required: true, displayHover: false,
},
displayHover: {
type: Boolean,
default: false,
},
},
setup(props) {
const imageUrl = `${getApiUrl()}workouts/map/${props.workout.map}`
return { imageUrl }
},
}) })
const { displayHover } = toRefs(props)
const imageUrl = `${getApiUrl()}workouts/map/${props.workout.map}`
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -30,15 +30,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import { ComputedRef, computed, ref, onBeforeMount, toRefs } from 'vue'
ComputedRef,
PropType,
computed,
defineComponent,
ref,
onBeforeMount,
} from 'vue'
import WorkoutCard from '@/components/Workout/WorkoutCard.vue' import WorkoutCard from '@/components/Workout/WorkoutCard.vue'
import NoWorkouts from '@/components/Workouts/NoWorkouts.vue' import NoWorkouts from '@/components/Workouts/NoWorkouts.vue'
@ -49,65 +42,44 @@
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { defaultOrder } from '@/utils/workouts' import { defaultOrder } from '@/utils/workouts'
export default defineComponent({ interface Props {
name: 'Timeline', sports: ISport[]
components: { user: IUserProfile
NoWorkouts, }
WorkoutCard, const props = defineProps<Props>()
},
props: {
sports: {
type: Object as PropType<ISport[]>,
required: true,
},
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
},
setup(props) {
const store = useStore()
let page = ref(1) const store = useStore()
const per_page = 5
const initWorkoutsCount =
props.user.nb_workouts >= per_page ? per_page : props.user.nb_workouts
onBeforeMount(() => loadWorkouts())
const workouts: ComputedRef<IWorkout[]> = computed( const { sports, user } = toRefs(props)
() => store.getters[WORKOUTS_STORE.GETTERS.TIMELINE_WORKOUTS] let page = ref(1)
) const per_page = 5
const moreWorkoutsExist: ComputedRef<boolean> = computed(() => const initWorkoutsCount =
workouts.value.length > 0 props.user.nb_workouts >= per_page ? per_page : props.user.nb_workouts
? workouts.value[workouts.value.length - 1].previous_workout !== null onBeforeMount(() => loadWorkouts())
: false const workouts: ComputedRef<IWorkout[]> = computed(
) () => store.getters[WORKOUTS_STORE.GETTERS.TIMELINE_WORKOUTS]
)
const moreWorkoutsExist: ComputedRef<boolean> = computed(() =>
workouts.value.length > 0
? workouts.value[workouts.value.length - 1].previous_workout !== null
: false
)
function loadWorkouts() { function loadWorkouts() {
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_TIMELINE_WORKOUTS, { store.dispatch(WORKOUTS_STORE.ACTIONS.GET_TIMELINE_WORKOUTS, {
page: page.value, page: page.value,
per_page, per_page,
...defaultOrder, ...defaultOrder,
}) })
} }
function loadMoreWorkouts() { function loadMoreWorkouts() {
page.value += 1 page.value += 1
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_MORE_TIMELINE_WORKOUTS, { store.dispatch(WORKOUTS_STORE.ACTIONS.GET_MORE_TIMELINE_WORKOUTS, {
page: page.value, page: page.value,
per_page, per_page,
...defaultOrder, ...defaultOrder,
}) })
} }
return {
initWorkoutsCount,
moreWorkoutsExist,
per_page,
workouts,
loadMoreWorkouts,
}
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -23,105 +23,71 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { addDays, format, isSameDay, isSameMonth, isToday } from 'date-fns' import { addDays, format, isSameDay, isSameMonth, isToday } from 'date-fns'
import { import { Ref, ref, toRefs, watch, onMounted } from 'vue'
PropType,
Ref,
defineComponent,
ref,
toRefs,
watch,
onMounted,
} from 'vue'
import CalendarWorkouts from '@/components/Dashboard/UserCalendar/CalendarWorkouts.vue' import CalendarWorkouts from '@/components/Dashboard/UserCalendar/CalendarWorkouts.vue'
import { ISport } from '@/types/sports' import { ISport } from '@/types/sports'
import { IWorkout } from '@/types/workouts' import { IWorkout } from '@/types/workouts'
import { getDateWithTZ } from '@/utils/dates' import { getDateWithTZ } from '@/utils/dates'
export default defineComponent({ interface Props {
name: 'CalendarCells', currentDay: Date
components: { endDate: Date
CalendarWorkouts, sports: ISport[]
}, startDate: Date
props: { timezone: string
currentDay: { weekStartingMonday: boolean
type: Date, workouts: IWorkout[]
required: true, }
}, const props = defineProps<Props>()
endDate: {
type: Date,
required: true,
},
sports: {
type: Object as PropType<ISport[]>,
required: true,
},
startDate: {
type: Date,
required: true,
},
timezone: {
type: String,
required: true,
},
weekStartingMonday: {
type: Boolean,
required: true,
},
workouts: {
type: Object as PropType<IWorkout[]>,
required: true,
},
},
setup(props) {
const rows: Ref<Date[][]> = ref([])
let { startDate, endDate, weekStartingMonday } = toRefs(props)
onMounted(() => getDays()) const {
currentDay,
endDate,
sports,
startDate,
timezone,
weekStartingMonday,
workouts,
} = toRefs(props)
const rows: Ref<Date[][]> = ref([])
function getDays() { onMounted(() => getDays())
rows.value = []
let day = startDate.value function getDays() {
while (day <= endDate.value) { rows.value = []
const days: Date[] = [] let day = startDate.value
for (let i = 0; i < 7; i++) { while (day <= endDate.value) {
days.push(day) const days: Date[] = []
day = addDays(day, 1) for (let i = 0; i < 7; i++) {
} days.push(day)
rows.value.push(days) day = addDays(day, 1)
}
} }
rows.value.push(days)
}
}
function isWeekEnd(day: number): boolean {
return weekStartingMonday.value
? [5, 6].includes(day)
: [0, 6].includes(day)
}
function filterWorkouts(day: Date, workouts: IWorkout[]) {
if (workouts) {
return workouts
.filter((workout) =>
isSameDay(getDateWithTZ(workout.workout_date, timezone), day)
)
.reverse()
}
return []
}
function isWeekEnd(day: number): boolean { watch(
return weekStartingMonday.value () => props.currentDay,
? [5, 6].includes(day) () => getDays()
: [0, 6].includes(day) )
}
function filterWorkouts(day: Date, workouts: IWorkout[]) {
if (workouts) {
return workouts
.filter((workout) =>
isSameDay(
getDateWithTZ(workout.workout_date, props.timezone),
day
)
)
.reverse()
}
return []
}
watch(
() => props.currentDay,
() => getDays()
)
return { rows, format, isSameMonth, isToday, isWeekEnd, filterWorkouts }
},
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -6,30 +6,19 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { format, addDays } from 'date-fns' import { format, addDays } from 'date-fns'
import { defineComponent } from 'vue'
export default defineComponent({ interface Props {
name: 'CalendarDays', startDate: Date
props: { localeOptions: string
startDate: { }
type: Date, const props = defineProps<Props>()
required: true,
}, const days = []
localeOptions: { for (let i = 0; i < 7; i++) {
type: String, days.push(addDays(props.startDate, i))
required: true, }
},
},
setup(props) {
const days = []
for (let i = 0; i < 7; i++) {
days.push(addDays(props.startDate, i))
}
return { days, addDays, format }
},
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -20,27 +20,19 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { format } from 'date-fns' import { format } from 'date-fns'
import { defineComponent } from 'vue' import { toRefs } from 'vue'
export default defineComponent({ interface Props {
name: 'CalendarHeader', day: Date
props: { localeOptions: string
day: { }
type: Date, const props = defineProps<Props>()
required: true,
}, const emit = defineEmits(['displayNextMonth', 'displayPreviousMonth'])
localeOptions: {
type: String, const { day, localeOptions } = toRefs(props)
required: true,
},
},
emits: ['displayNextMonth', 'displayPreviousMonth'],
setup(props, { emit }) {
return { emit, format }
},
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -13,7 +13,7 @@
aria-hidden="true" aria-hidden="true"
:title=" :title="
workout.records.map( workout.records.map(
(record) => ` ${t(`workouts.RECORD_${record.record_type}`)}` (record) => ` ${$t(`workouts.RECORD_${record.record_type}`)}`
) )
" "
/> />
@ -21,29 +21,17 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, PropType } from 'vue' import { toRefs } from 'vue'
import { useI18n } from 'vue-i18n'
import { IWorkout } from '@/types/workouts' import { IWorkout } from '@/types/workouts'
interface Props {
workout: IWorkout
sportLabel: string
}
const props = defineProps<Props>()
export default defineComponent({ const { workout, sportLabel } = toRefs(props)
name: 'CalendarWorkout',
props: {
workout: {
type: Object as PropType<IWorkout>,
required: true,
},
sportLabel: {
type: String,
required: true,
},
},
setup() {
const { t } = useI18n()
return { t }
},
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -34,8 +34,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, computed, defineComponent } from 'vue' import { computed, toRefs } from 'vue'
import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue' import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue'
import CalendarWorkoutsChart from '@/components/Dashboard/UserCalendar/CalendarWorkoutsChart.vue' import CalendarWorkoutsChart from '@/components/Dashboard/UserCalendar/CalendarWorkoutsChart.vue'
@ -44,31 +44,16 @@
import { getSportLabel, sportIdColors } from '@/utils/sports' import { getSportLabel, sportIdColors } from '@/utils/sports'
import { getDonutDatasets } from '@/utils/workouts' import { getDonutDatasets } from '@/utils/workouts'
export default defineComponent({ interface Props {
name: 'CalendarWorkouts', workouts: IWorkout[]
components: { sports: ISport[]
CalendarWorkout, }
CalendarWorkoutsChart, const props = defineProps<Props>()
},
props: { const { workouts, sports } = toRefs(props)
workouts: { const chartDatasets = computed(() => getDonutDatasets(props.workouts))
type: Object as PropType<IWorkout[]>, const colors = computed(() => sportIdColors(props.sports))
required: true, const displayedWorkoutCount = 6
},
sports: {
type: Object as PropType<ISport[]>,
required: true,
},
},
setup(props) {
return {
chartDatasets: computed(() => getDonutDatasets(props.workouts)),
colors: computed(() => sportIdColors(props.sports)),
displayedWorkoutCount: 6,
getSportLabel,
}
},
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -22,8 +22,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent, ref } from 'vue' import { ref, toRefs } from 'vue'
import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue' import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue'
import DonutChart from '@/components/Dashboard/UserCalendar/DonutChart.vue' import DonutChart from '@/components/Dashboard/UserCalendar/DonutChart.vue'
@ -31,39 +31,21 @@
import { IWorkout } from '@/types/workouts' import { IWorkout } from '@/types/workouts'
import { getSportLabel } from '@/utils/sports' import { getSportLabel } from '@/utils/sports'
export default defineComponent({ interface Props {
name: 'CalendarWorkoutsChart', colors: Record<number, string>
components: { datasets: Record<number, Record<string, number>>
CalendarWorkout, sports: ISport[]
DonutChart, workouts: IWorkout[]
}, }
props: { const props = defineProps<Props>()
colors: {
type: Object as PropType<Record<number, string>>, const { colors, datasets, sports, workouts } = toRefs(props)
required: true, const isHidden = ref(true)
},
datasets: { function togglePane(event: Event & { target: HTMLElement }) {
type: Object as PropType<Record<number, Record<string, number>>>, event.stopPropagation()
required: true, isHidden.value = !isHidden.value
}, }
sports: {
type: Object as PropType<ISport[]>,
required: true,
},
workouts: {
type: Object as PropType<IWorkout[]>,
required: true,
},
},
setup() {
const isHidden = ref(true)
function togglePane(event: Event & { target: HTMLElement }) {
event.stopPropagation()
isHidden.value = !isHidden.value
}
return { isHidden, getSportLabel, togglePane }
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -22,52 +22,34 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent } from 'vue' import { toRefs } from 'vue'
export default defineComponent({ interface Props {
name: 'DonutChart', colors: Record<number, string>
props: { datasets: Record<number, Record<string, number>>
colors: { }
type: Object as PropType<Record<number, string>>, const props = defineProps<Props>()
required: true,
},
datasets: {
type: Object as PropType<Record<number, Record<string, number>>>,
required: true,
},
},
setup() {
let angleOffset = -90
const cx = 16
const cy = 16
const radius = 14
const circumference = 2 * Math.PI * radius
function calculateStrokeDashOffset( const { colors, datasets } = toRefs(props)
percentage: number, let angleOffset = -90
circumference: number const cx = 16
): number { const cy = 16
return circumference - percentage * circumference const radius = 14
} const circumference = 2 * Math.PI * radius
function returnCircleTransformValue(
index: number,
percentage: number
): string {
const rotation = `rotate(${angleOffset}, ${cx}, ${cy})`
angleOffset = percentage * 360 + angleOffset
return rotation
}
return { function calculateStrokeDashOffset(
angleOffset, percentage: number,
circumference, circumference: number
cx, ): number {
cy, return circumference - percentage * circumference
radius, }
calculateStrokeDashOffset, function returnCircleTransformValue(
returnCircleTransformValue, index: number,
} percentage: number
}, ): string {
}) const rotation = `rotate(${angleOffset}, ${cx}, ${cy})`
angleOffset = percentage * 360 + angleOffset
return rotation
}
</script> </script>

View File

@ -21,16 +21,9 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { addMonths, format, subMonths } from 'date-fns' import { addMonths, format, subMonths } from 'date-fns'
import { import { ComputedRef, computed, ref, toRefs, onBeforeMount } from 'vue'
ComputedRef,
PropType,
computed,
defineComponent,
ref,
onBeforeMount,
} from 'vue'
import CalendarCells from '@/components/Dashboard/UserCalendar/CalendarCells.vue' import CalendarCells from '@/components/Dashboard/UserCalendar/CalendarCells.vue'
import CalendarDays from '@/components/Dashboard/UserCalendar/CalendarDays.vue' import CalendarDays from '@/components/Dashboard/UserCalendar/CalendarDays.vue'
@ -43,70 +36,43 @@
import { getCalendarStartAndEnd } from '@/utils/dates' import { getCalendarStartAndEnd } from '@/utils/dates'
import { defaultOrder } from '@/utils/workouts' import { defaultOrder } from '@/utils/workouts'
export default defineComponent({ interface Props {
name: 'UserCalendar', sports: ISport[]
components: { user: IUserProfile
CalendarCells, }
CalendarDays, const props = defineProps<Props>()
CalendarHeader,
},
props: {
sports: {
type: Object as PropType<ISport[]>,
required: true,
},
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
},
setup(props) {
const store = useStore()
onBeforeMount(() => getCalendarWorkouts()) const store = useStore()
const dateFormat = 'yyyy-MM-dd' const { sports, user } = toRefs(props)
let day = ref(new Date()) const dateFormat = 'yyyy-MM-dd'
let calendarDates = ref( let day = ref(new Date())
getCalendarStartAndEnd(day.value, props.user.weekm) let calendarDates = ref(getCalendarStartAndEnd(day.value, props.user.weekm))
) const calendarWorkouts: ComputedRef<IWorkout[]> = computed(
const calendarWorkouts: ComputedRef<IWorkout[]> = computed( () => store.getters[WORKOUTS_STORE.GETTERS.CALENDAR_WORKOUTS]
() => store.getters[WORKOUTS_STORE.GETTERS.CALENDAR_WORKOUTS] )
)
function getCalendarWorkouts() { onBeforeMount(() => getCalendarWorkouts())
calendarDates.value = getCalendarStartAndEnd(
day.value,
props.user.weekm
)
const apiParams: TWorkoutsPayload = {
from: format(calendarDates.value.start, dateFormat),
to: format(calendarDates.value.end, dateFormat),
page: 1,
per_page: 100,
...defaultOrder,
}
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_CALENDAR_WORKOUTS, apiParams)
}
function displayNextMonth() { function getCalendarWorkouts() {
day.value = addMonths(day.value, 1) calendarDates.value = getCalendarStartAndEnd(day.value, props.user.weekm)
getCalendarWorkouts() const apiParams: TWorkoutsPayload = {
} from: format(calendarDates.value.start, dateFormat),
function displayPreviousMonth() { to: format(calendarDates.value.end, dateFormat),
day.value = subMonths(day.value, 1) page: 1,
getCalendarWorkouts() per_page: 100,
} ...defaultOrder,
}
return { store.dispatch(WORKOUTS_STORE.ACTIONS.GET_CALENDAR_WORKOUTS, apiParams)
day, }
calendarDates, function displayNextMonth() {
calendarWorkouts, day.value = addMonths(day.value, 1)
displayNextMonth, getCalendarWorkouts()
displayPreviousMonth, }
} function displayPreviousMonth() {
}, day.value = subMonths(day.value, 1)
}) getCalendarWorkouts()
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -15,41 +15,28 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { endOfMonth, startOfMonth } from 'date-fns' import { endOfMonth, startOfMonth } from 'date-fns'
import { PropType, defineComponent } from 'vue' import { toRefs } from 'vue'
import StatChart from '@/components/Common/StatsChart/index.vue' import StatChart from '@/components/Common/StatsChart/index.vue'
import { ISport } from '@/types/sports' import { ISport } from '@/types/sports'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
export default defineComponent({ interface Props {
name: 'UserMonthStats', sports: ISport[]
components: { user: IUserProfile
StatChart, }
}, const props = defineProps<Props>()
props: {
sports: { const { sports, user } = toRefs(props)
type: Object as PropType<ISport[]>, const date = new Date()
required: true, const chartParams = {
}, duration: 'week',
user: { start: startOfMonth(date),
type: Object as PropType<IUserProfile>, end: endOfMonth(date),
required: true, }
}, const selectedSportIds = props.sports.map((sport) => sport.id)
},
setup(props) {
const date = new Date()
return {
chartParams: {
duration: 'week',
start: startOfMonth(date),
end: endOfMonth(date),
},
selectedSportIds: props.sports.map((sport) => sport.id),
}
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -7,9 +7,9 @@
</template> </template>
<template #content> <template #content>
<div class="record" v-for="record in records.records" :key="record.id"> <div class="record" v-for="record in records.records" :key="record.id">
<span class="record-type">{{ <span class="record-type">
t(`workouts.RECORD_${record.record_type}`) {{ $t(`workouts.RECORD_${record.record_type}`) }}
}}</span> </span>
<span class="record-value">{{ record.value }}</span> <span class="record-value">{{ record.value }}</span>
<span class="record-date"> <span class="record-date">
<router-link <router-link
@ -17,8 +17,9 @@
name: 'Workout', name: 'Workout',
params: { workoutId: record.workout_id }, params: { workoutId: record.workout_id },
}" }"
>{{ record.workout_date }}</router-link
> >
{{ record.workout_date }}
</router-link>
</span> </span>
</div> </div>
</template> </template>
@ -26,29 +27,18 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent } from 'vue' import { toRefs } from 'vue'
import { useI18n } from 'vue-i18n'
import { IRecord } from '@/types/workouts' import { IRecordsBySports } from '@/types/workouts'
export default defineComponent({ interface Props {
name: 'RecordsCard', records: IRecordsBySports
props: { sportTranslatedLabel: string
records: { }
type: Object as PropType<IRecord[]>, const props = defineProps<Props>()
required: true,
}, const { records, sportTranslatedLabel } = toRefs(props)
sportTranslatedLabel: {
type: String,
required: true,
},
},
setup() {
const { t } = useI18n()
return { t }
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -18,8 +18,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { computed, defineComponent, PropType } from 'vue' import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import RecordsCard from '@/components/Dashboard/UserRecords/RecordsCard.vue' import RecordsCard from '@/components/Dashboard/UserRecords/RecordsCard.vue'
@ -28,33 +28,21 @@
import { getRecordsBySports } from '@/utils/records' import { getRecordsBySports } from '@/utils/records'
import { translateSports } from '@/utils/sports' import { translateSports } from '@/utils/sports'
export default defineComponent({ interface Props {
name: 'UserRecords', sports: ISport[]
components: { user: IUserProfile
RecordsCard, }
}, const props = defineProps<Props>()
props: {
sports: { const { t } = useI18n()
type: Object as PropType<ISport[]>,
required: true, const recordsBySport = computed(() =>
}, getRecordsBySports(
user: { props.user.records,
type: Object as PropType<IUserProfile>, translateSports(props.sports, t),
required: true, props.user.timezone
}, )
}, )
setup(props) {
const { t } = useI18n()
const recordsBySport = computed(() =>
getRecordsBySports(
props.user.records,
translateSports(props.sports, t),
props.user.timezone
)
)
return { recordsBySport }
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -12,8 +12,8 @@
/> />
<StatCard <StatCard
icon="clock-o" icon="clock-o"
:value="total_duration.days" :value="totalDuration.days"
:text="total_duration.duration" :text="totalDuration.duration"
/> />
<StatCard <StatCard
icon="tags" icon="tags"
@ -23,49 +23,40 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ComputedRef, PropType, defineComponent, computed } from 'vue' import { ComputedRef, computed, toRefs } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import StatCard from '@/components/Common/StatCard.vue' import StatCard from '@/components/Common/StatCard.vue'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
interface Props {
user: IUserProfile
}
const props = defineProps<Props>()
export default defineComponent({ const { t } = useI18n()
name: 'UserStatsCards',
components: {
StatCard,
},
props: {
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
},
setup(props) {
const { t } = useI18n()
const total_duration: ComputedRef<string> = computed(
() => props.user.total_duration
)
function get_duration(total_duration: ComputedRef<string>) { const { user } = toRefs(props)
const duration = total_duration.value.match(/day/g) const userTotalDuration: ComputedRef<string> = computed(
? total_duration.value.split(', ')[1] () => props.user.total_duration
: total_duration.value )
return { const totalDuration = computed(() => get_duration(userTotalDuration))
days: total_duration.value.match(/day/g)
? `${total_duration.value.split(' ')[0]} ${
total_duration.value.match(/days/g)
? t('common.DAY', 2)
: t('common.DAY', 1)
}`
: `0 ${t('common.DAY', 2)},`,
duration: `${duration.split(':')[0]}h ${duration.split(':')[1]}min`,
}
}
return { total_duration: computed(() => get_duration(total_duration)) } function get_duration(total_duration: ComputedRef<string>) {
}, const duration = total_duration.value.match(/day/g)
}) ? total_duration.value.split(', ')[1]
: total_duration.value
return {
days: total_duration.value.match(/day/g)
? `${total_duration.value.split(' ')[0]} ${
total_duration.value.match(/days/g)
? t('common.DAY', 2)
: t('common.DAY', 1)
}`
: `0 ${t('common.DAY', 2)},`,
duration: `${duration.split(':')[0]}h ${duration.split(':')[1]}min`,
}
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -37,18 +37,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs } from 'vue'
export default defineComponent({ interface Props {
name: 'Footer', version: string
props: { }
version: { const props = defineProps<Props>()
type: String,
required: true, const { version } = toRefs(props)
},
},
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -77,8 +77,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ComputedRef, computed, defineComponent, ref, capitalize } from 'vue' import { ComputedRef, computed, ref, capitalize } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import UserPicture from '@/components/User/UserPicture.vue' import UserPicture from '@/components/User/UserPicture.vue'
@ -86,68 +86,39 @@
import { IDropdownOption } from '@/types/forms' import { IDropdownOption } from '@/types/forms'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getApiUrl } from '@/utils'
import { availableLanguages } from '@/utils/locales' import { availableLanguages } from '@/utils/locales'
export default defineComponent({ const emit = defineEmits(['menuInteraction'])
name: 'NavBar',
components: {
UserPicture,
},
emits: ['menuInteraction'],
setup(props, { emit }) {
const { locale } = useI18n()
const store = useStore()
const authUser: ComputedRef<IUserProfile> = computed( const { locale } = useI18n()
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE] const store = useStore()
)
const isAuthenticated: ComputedRef<boolean> = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED]
)
const authUserPictureUrl: ComputedRef<string> = computed(() =>
isAuthenticated.value && authUser.value.picture
? `${getApiUrl()}/users/${
authUser.value.username
}/picture?${Date.now()}`
: ''
)
const language: ComputedRef<string> = computed(
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
)
let isMenuOpen = ref(false)
function openMenu() { const authUser: ComputedRef<IUserProfile> = computed(
isMenuOpen.value = true () => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
emit('menuInteraction', true) )
} const isAuthenticated: ComputedRef<boolean> = computed(
function closeMenu() { () => store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED]
isMenuOpen.value = false )
emit('menuInteraction', false) const language: ComputedRef<string> = computed(
} () => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
function updateLanguage(option: IDropdownOption) { )
locale.value = option.value.toString() let isMenuOpen = ref(false)
store.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, option.value)
}
function logout() {
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
}
return { function openMenu() {
availableLanguages, isMenuOpen.value = true
authUser, emit('menuInteraction', true)
authUserPictureUrl, }
isAuthenticated, function closeMenu() {
isMenuOpen, isMenuOpen.value = false
language, emit('menuInteraction', false)
capitalize, }
openMenu, function updateLanguage(option: IDropdownOption) {
closeMenu, locale.value = option.value.toString()
updateLanguage, store.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, option.value)
logout, }
} function logout() {
}, store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
}) }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -28,19 +28,6 @@
</div> </div>
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
export default defineComponent({
name: 'NoConfig',
setup() {
const { t } = useI18n()
return { t }
},
})
</script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '~@/scss/base'; @import '~@/scss/base';

View File

@ -37,29 +37,18 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue' import { ref } from 'vue'
export default defineComponent({ const emit = defineEmits(['arrowClick', 'timeFrameUpdate'])
name: 'StatsMenu',
emits: ['arrowClick', 'timeFrameUpdate'],
setup(props, { emit }) {
let selectedTimeFrame = ref('month')
const timeFrames = ['week', 'month', 'year']
function onUpdateTimeFrame(timeFrame: string) { let selectedTimeFrame = ref('month')
selectedTimeFrame.value = timeFrame const timeFrames = ['week', 'month', 'year']
emit('timeFrameUpdate', timeFrame)
}
return { function onUpdateTimeFrame(timeFrame: string) {
selectedTimeFrame, selectedTimeFrame.value = timeFrame
timeFrames, emit('timeFrameUpdate', timeFrame)
onUpdateTimeFrame, }
emit,
}
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -19,43 +19,34 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ComputedRef, PropType, computed, defineComponent, inject } from 'vue' import { ComputedRef, computed, inject, withDefaults, toRefs } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ISport, ITranslatedSport } from '@/types/sports' import { ISport, ITranslatedSport } from '@/types/sports'
import { translateSports } from '@/utils/sports' import { translateSports } from '@/utils/sports'
export default defineComponent({ interface Props {
name: 'SportsMenu', userSports: ISport[]
props: { selectedSportIds?: number[]
selectedSportIds: { }
type: Array as PropType<number[]>, const props = withDefaults(defineProps<Props>(), {
default: () => [], selectedSportIds: () => [],
},
userSports: {
type: Object as PropType<ISport[]>,
required: true,
},
},
emits: ['selectedSportIdsUpdate'],
setup(props, { emit }) {
const { t } = useI18n()
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(props.userSports, t)
)
function updateSelectedSportIds(sportId: number) {
emit('selectedSportIdsUpdate', sportId)
}
return {
sportColors: inject('sportColors'),
translatedSports,
updateSelectedSportIds,
}
},
}) })
const emit = defineEmits(['selectedSportIdsUpdate'])
const { t } = useI18n()
const sportColors = inject('sportColors')
const { selectedSportIds } = toRefs(props)
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(props.userSports, t)
)
function updateSelectedSportIds(sportId: number) {
emit('selectedSportIdsUpdate', sportId)
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -19,16 +19,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import { ComputedRef, Ref, computed, ref, toRefs, watch } from 'vue'
ComputedRef,
PropType,
Ref,
computed,
defineComponent,
ref,
watch,
} from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import StatChart from '@/components/Common/StatsChart/index.vue' import StatChart from '@/components/Common/StatsChart/index.vue'
@ -40,81 +32,57 @@
import { translateSports } from '@/utils/sports' import { translateSports } from '@/utils/sports'
import { getStatsDateParams, updateChartParams } from '@/utils/statistics' import { getStatsDateParams, updateChartParams } from '@/utils/statistics'
export default defineComponent({ interface Props {
name: 'Statistics', sports: ISport[]
components: { user: IUserProfile
SportsMenu, }
StatChart, const props = defineProps<Props>()
StatsMenu,
},
props: {
sports: {
type: Object as PropType<ISport[]>,
required: true,
},
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
},
setup(props) {
const { t } = useI18n()
let selectedTimeFrame = ref('month')
const timeFrames = ['week', 'month', 'year']
const chartParams: Ref<IStatisticsDateParams> = ref(
getChartParams(selectedTimeFrame.value)
)
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(props.sports, t)
)
const selectedSportIds: Ref<number[]> = ref(getSports(props.sports))
function updateTimeFrame(timeFrame: string) { const { t } = useI18n()
selectedTimeFrame.value = timeFrame
chartParams.value = getChartParams(selectedTimeFrame.value)
}
function getChartParams(timeFrame: string): IStatisticsDateParams {
return getStatsDateParams(new Date(), timeFrame, props.user.weekm)
}
function handleOnClickArrows(backward: boolean) {
chartParams.value = updateChartParams(
chartParams.value,
backward,
props.user.weekm
)
}
function getSports(sports: ISport[]) {
return sports.map((sport) => sport.id)
}
function updateSelectedSportIds(sportId: number) {
if (selectedSportIds.value.includes(sportId)) {
selectedSportIds.value = selectedSportIds.value.filter(
(id) => id !== sportId
)
} else {
selectedSportIds.value.push(sportId)
}
}
watch( const { sports, user } = toRefs(props)
() => props.sports, let selectedTimeFrame = ref('month')
(newSports) => { const chartParams: Ref<IStatisticsDateParams> = ref(
selectedSportIds.value = getSports(newSports) getChartParams(selectedTimeFrame.value)
} )
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(props.sports, t)
)
const selectedSportIds: Ref<number[]> = ref(getSports(props.sports))
function updateTimeFrame(timeFrame: string) {
selectedTimeFrame.value = timeFrame
chartParams.value = getChartParams(selectedTimeFrame.value)
}
function getChartParams(timeFrame: string): IStatisticsDateParams {
return getStatsDateParams(new Date(), timeFrame, props.user.weekm)
}
function handleOnClickArrows(backward: boolean) {
chartParams.value = updateChartParams(
chartParams.value,
backward,
props.user.weekm
)
}
function getSports(sports: ISport[]) {
return sports.map((sport) => sport.id)
}
function updateSelectedSportIds(sportId: number) {
if (selectedSportIds.value.includes(sportId)) {
selectedSportIds.value = selectedSportIds.value.filter(
(id) => id !== sportId
) )
} else {
selectedSportIds.value.push(sportId)
}
}
return { watch(
chartParams, () => props.sports,
selectedTimeFrame, (newSports) => {
timeFrames, selectedSportIds.value = getSports(newSports)
translatedSports, }
selectedSportIds, )
handleOnClickArrows,
updateSelectedSportIds,
updateTimeFrame,
}
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -15,25 +15,18 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs } from 'vue'
import EmailSent from '@/components/Common/Images/EmailSent.vue' import EmailSent from '@/components/Common/Images/EmailSent.vue'
import Password from '@/components/Common/Images/Password.vue' import Password from '@/components/Common/Images/Password.vue'
export default defineComponent({ interface Props {
name: 'PasswordActionDone', action: string
components: { }
EmailSent, const props = defineProps<Props>()
Password,
}, const { action } = toRefs(props)
props: {
action: {
type: String,
required: true,
},
},
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -9,27 +9,20 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue' import { toRefs, withDefaults } from 'vue'
import UserAuthForm from '@/components/User/UserAuthForm.vue' import UserAuthForm from '@/components/User/UserAuthForm.vue'
export default defineComponent({ interface Props {
name: 'PasswordResetForm', action: string
components: { token?: string
UserAuthForm, }
}, const props = withDefaults(defineProps<Props>(), {
props: { token: '',
action: {
type: String,
required: true,
},
token: {
type: String,
default: '',
},
},
}) })
const { action, token } = toRefs(props)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -27,24 +27,18 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent } from 'vue' import { toRefs } from 'vue'
import UserPicture from '@/components/User/UserPicture.vue' import UserPicture from '@/components/User/UserPicture.vue'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
export default defineComponent({ interface Props {
name: 'ProfileDisplay', user: IUserProfile
components: { }
UserPicture, const props = defineProps<Props>()
},
props: { const { user } = toRefs(props)
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -43,67 +43,46 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { format } from 'date-fns' import { format } from 'date-fns'
import { import { ComputedRef, Ref, computed, ref, toRefs, withDefaults } from 'vue'
ComputedRef,
PropType,
Ref,
computed,
defineComponent,
ref,
} from 'vue'
import { AUTH_USER_STORE } from '@/store/constants' import { AUTH_USER_STORE } from '@/store/constants'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
export default defineComponent({ interface Props {
name: 'UserInfos', user: IUserProfile
props: { fromAdmin?: boolean
user: { }
type: Object as PropType<IUserProfile>, const props = withDefaults(defineProps<Props>(), {
required: true, fromAdmin: false,
},
fromAdmin: {
type: Boolean,
default: false,
},
},
setup(props) {
const store = useStore()
const authUser: ComputedRef<IUserProfile> = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
)
const registrationDate = computed(() =>
props.user.created_at
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
: ''
)
const birthDate = computed(() =>
props.user.birth_date
? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
: ''
)
let displayModal: Ref<boolean> = ref(false)
function updateDisplayModal(value: boolean) {
displayModal.value = value
}
function deleteUserAccount(username: string) {
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
}
return {
authUser,
birthDate,
displayModal,
registrationDate,
deleteUserAccount,
updateDisplayModal,
}
},
}) })
const store = useStore()
const { user, fromAdmin } = toRefs(props)
const authUser: ComputedRef<IUserProfile> = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
)
const registrationDate = computed(() =>
props.user.created_at
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
: ''
)
const birthDate = computed(() =>
props.user.birth_date
? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
: ''
)
let displayModal: Ref<boolean> = ref(false)
function updateDisplayModal(value: boolean) {
displayModal.value = value
}
function deleteUserAccount(username: string) {
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -17,35 +17,26 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, computed, defineComponent } from 'vue' import { computed } from 'vue'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
import { languageLabels } from '@/utils/locales' import { languageLabels } from '@/utils/locales'
export default defineComponent({ interface Props {
name: 'UserPreferences', user: IUserProfile
props: { }
user: { const props = defineProps<Props>()
type: Object as PropType<IUserProfile>,
required: true, const language = computed(() =>
}, props.user.language
}, ? languageLabels[props.user.language]
setup(props) { : languageLabels['en']
const language = computed(() => )
props.user.language const fistDayOfWeek = computed(() => (props.user.weekm ? 'MONDAY' : 'SUNDAY'))
? languageLabels[props.user.language] const timezone = computed(() =>
: languageLabels['en'] props.user.timezone ? props.user.timezone : 'Europe/Paris'
) )
const fistDayOfWeek = computed(() =>
props.user.weekm ? 'MONDAY' : 'SUNDAY'
)
const timezone = computed(() =>
props.user.timezone ? props.user.timezone : 'Europe/Paris'
)
return { fistDayOfWeek, language, timezone }
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -8,35 +8,21 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent } from 'vue' import { toRefs } from 'vue'
import UserHeader from '@/components/User/ProfileDisplay/UserHeader.vue' import UserHeader from '@/components/User/ProfileDisplay/UserHeader.vue'
import UserProfileTabs from '@/components/User/UserProfileTabs.vue' import UserProfileTabs from '@/components/User/UserProfileTabs.vue'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
export default defineComponent({ interface Props {
name: 'ProfileDisplay', user: IUserProfile
components: { tab: string
UserHeader, }
UserProfileTabs, const props = defineProps<Props>()
},
props: { const { user, tab } = toRefs(props)
user: { const tabs = ['PROFILE', 'PREFERENCES']
type: Object as PropType<IUserProfile>,
required: true,
},
tab: {
type: String,
required: true,
},
},
setup() {
return {
tabs: ['PROFILE', 'PREFERENCES'],
}
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -27,74 +27,56 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { Ref, defineComponent, ref, watch } from 'vue' import { Ref, ref, toRefs, watch, withDefaults } from 'vue'
import { timeZones } from '@/utils/timezone' import { timeZones } from '@/utils/timezone'
export default defineComponent({ interface Props {
name: 'TimezoneDropdown', input: string
props: { disabled?: boolean
disabled: { }
type: Boolean, const props = withDefaults(defineProps<Props>(), {
default: false, disabled: false,
},
input: {
type: String,
required: true,
},
},
emits: ['updateTimezone'],
setup(props, { emit }) {
const timezone: Ref<string> = ref(props.input)
const isOpen: Ref<boolean> = ref(false)
const tzList: Ref<HTMLInputElement | null> = ref(null)
const focusItemIndex: Ref<number> = ref(0)
function matchTimezone(t: string): RegExpMatchArray | null {
return t.toLowerCase().match(timezone.value.toLowerCase())
}
function onMouseOver(index: number) {
focusItemIndex.value = index
}
function onUpdateTimezone(value: string) {
timezone.value = value
isOpen.value = false
emit('updateTimezone', value)
}
function onEnter(event: Event & { target: HTMLInputElement }) {
event.preventDefault()
if (tzList.value?.firstElementChild?.innerHTML) {
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
}
}
function openDropdown(event: Event & { target: HTMLInputElement }) {
event.preventDefault()
isOpen.value = true
timezone.value = event.target.value.trim()
}
watch(
() => props.input,
(value) => {
timezone.value = value
}
)
return {
focusItemIndex,
isOpen,
timezone,
timeZones,
tzList,
matchTimezone,
onEnter,
onMouseOver,
onUpdateTimezone,
openDropdown,
}
},
}) })
const emit = defineEmits(['updateTimezone'])
const { input, disabled } = toRefs(props)
const timezone: Ref<string> = ref(props.input)
const isOpen: Ref<boolean> = ref(false)
const tzList: Ref<HTMLInputElement | null> = ref(null)
const focusItemIndex: Ref<number> = ref(0)
function matchTimezone(t: string): RegExpMatchArray | null {
return t.toLowerCase().match(timezone.value.toLowerCase())
}
function onMouseOver(index: number) {
focusItemIndex.value = index
}
function onUpdateTimezone(value: string) {
timezone.value = value
isOpen.value = false
emit('updateTimezone', value)
}
function onEnter(event: Event & { target: HTMLInputElement }) {
event.preventDefault()
if (tzList.value?.firstElementChild?.innerHTML) {
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
}
}
function openDropdown(event: Event & { target: HTMLInputElement }) {
event.preventDefault()
isOpen.value = true
timezone.value = event.target.value.trim()
}
watch(
() => props.input,
(value) => {
timezone.value = value
}
)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -93,16 +93,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { format } from 'date-fns' import { format } from 'date-fns'
import { import {
ComputedRef, ComputedRef,
PropType,
Ref, Ref,
computed, computed,
defineComponent,
reactive, reactive,
ref, ref,
toRefs,
onMounted, onMounted,
} from 'vue' } from 'vue'
@ -110,79 +109,63 @@
import { IUserProfile, IUserPayload } from '@/types/user' import { IUserProfile, IUserPayload } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
export default defineComponent({ interface Props {
name: 'UserInfosEdition', user: IUserProfile
props: { }
user: { const props = defineProps<Props>()
type: Object as PropType<IUserProfile>,
required: true,
},
},
setup(props) {
const store = useStore()
const userForm: IUserPayload = reactive({
password: '',
password_conf: '',
first_name: '',
last_name: '',
birth_date: '',
location: '',
bio: '',
})
const registrationDate = computed(() =>
props.user.created_at
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
: ''
)
const loading = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
let displayModal: Ref<boolean> = ref(false)
onMounted(() => { const store = useStore()
if (props.user) {
updateUserForm(props.user)
}
})
function updateUserForm(user: IUserProfile) { const { user } = toRefs(props)
userForm.first_name = user.first_name ? user.first_name : '' const userForm: IUserPayload = reactive({
userForm.last_name = user.last_name ? user.last_name : '' password: '',
userForm.birth_date = user.birth_date password_conf: '',
? format(new Date(user.birth_date), 'yyyy-MM-dd') first_name: '',
: '' last_name: '',
userForm.location = user.location ? user.location : '' birth_date: '',
userForm.bio = user.bio ? user.bio : '' location: '',
} bio: '',
function updateBio(value: string) {
userForm.bio = value
}
function updateProfile() {
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
}
function updateDisplayModal(value: boolean) {
displayModal.value = value
}
function deleteAccount(username: string) {
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
}
return {
displayModal,
errorMessages,
loading,
registrationDate,
userForm,
deleteAccount,
updateBio,
updateDisplayModal,
updateProfile,
}
},
}) })
const registrationDate = computed(() =>
props.user.created_at
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
: ''
)
const loading = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
let displayModal: Ref<boolean> = ref(false)
onMounted(() => {
if (props.user) {
updateUserForm(props.user)
}
})
function updateUserForm(user: IUserProfile) {
userForm.first_name = user.first_name ? user.first_name : ''
userForm.last_name = user.last_name ? user.last_name : ''
userForm.birth_date = user.birth_date
? format(new Date(user.birth_date), 'yyyy-MM-dd')
: ''
userForm.location = user.location ? user.location : ''
userForm.bio = user.bio ? user.bio : ''
}
function updateBio(value: string) {
userForm.bio = value
}
function updateProfile() {
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
}
function updateDisplayModal(value: boolean) {
displayModal.value = value
}
function deleteAccount(username: string) {
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -32,15 +32,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import { ComputedRef, Ref, computed, ref, toRefs } from 'vue'
ComputedRef,
PropType,
Ref,
defineComponent,
computed,
ref,
} from 'vue'
import UserPicture from '@/components/User/UserPicture.vue' import UserPicture from '@/components/User/UserPicture.vue'
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants' import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
@ -49,56 +42,40 @@
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getReadableFileSize } from '@/utils/files' import { getReadableFileSize } from '@/utils/files'
export default defineComponent({ interface Props {
name: 'UserPictureEdition', user: IUserProfile
components: { }
UserPicture, const props = defineProps<Props>()
},
props: {
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
},
setup() {
const store = useStore()
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
const appConfig: ComputedRef<TAppConfig> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
)
const fileSizeLimit = appConfig.value.max_single_file_size
? getReadableFileSize(appConfig.value.max_single_file_size)
: ''
let pictureFile: Ref<File | null> = ref(null)
function deleteUserPicture() { const store = useStore()
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_PICTURE)
}
function updatePictureFile(event: Event & { target: HTMLInputElement }) {
if (event.target.files) {
pictureFile.value = event.target.files[0]
}
}
function updateUserPicture() {
if (pictureFile.value) {
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE, {
picture: pictureFile.value,
})
}
}
return { const { user } = toRefs(props)
errorMessages, const errorMessages: ComputedRef<string | string[] | null> = computed(
fileSizeLimit, () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
pictureFile, )
deleteUserPicture, const appConfig: ComputedRef<TAppConfig> = computed(
updateUserPicture, () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
updatePictureFile, )
} const fileSizeLimit = appConfig.value.max_single_file_size
}, ? getReadableFileSize(appConfig.value.max_single_file_size)
}) : ''
let pictureFile: Ref<File | null> = ref(null)
function deleteUserPicture() {
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_PICTURE)
}
function updatePictureFile(event: Event & { target: HTMLInputElement }) {
if (event.target.files) {
pictureFile.value = event.target.files[0]
}
}
function updateUserPicture() {
if (pictureFile.value) {
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE, {
picture: pictureFile.value,
})
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -51,15 +51,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import { ComputedRef, computed, reactive, onMounted } from 'vue'
ComputedRef,
PropType,
computed,
defineComponent,
reactive,
onMounted,
} from 'vue'
import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue' import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue'
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants' import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
@ -67,71 +60,50 @@
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { availableLanguages } from '@/utils/locales' import { availableLanguages } from '@/utils/locales'
export default defineComponent({ interface Props {
name: 'UserPreferencesEdition', user: IUserProfile
components: { }
TimezoneDropdown, const props = defineProps<Props>()
},
props: {
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
},
setup(props) {
const store = useStore()
const userForm: IUserPreferencesPayload = reactive({
language: '',
timezone: 'Europe/Paris',
weekm: false,
})
const weekStart = [
{
label: 'MONDAY',
value: true,
},
{
label: 'SUNDAY',
value: false,
},
]
const loading = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
onMounted(() => { const store = useStore()
if (props.user) {
updateUserForm(props.user)
}
})
function updateUserForm(user: IUserProfile) { const userForm: IUserPreferencesPayload = reactive({
userForm.language = user.language ? user.language : 'en' language: '',
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris' timezone: 'Europe/Paris',
userForm.weekm = user.weekm ? user.weekm : false weekm: false,
}
function updateProfile() {
store.dispatch(
AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES,
userForm
)
}
function updateTZ(value: string) {
userForm.timezone = value
}
return {
availableLanguages,
errorMessages,
loading,
userForm,
weekStart,
updateProfile,
updateTZ,
}
},
}) })
const weekStart = [
{
label: 'MONDAY',
value: true,
},
{
label: 'SUNDAY',
value: false,
},
]
const loading = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
onMounted(() => {
if (props.user) {
updateUserForm(props.user)
}
})
function updateUserForm(user: IUserProfile) {
userForm.language = user.language ? user.language : 'en'
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
userForm.weekm = user.weekm ? user.weekm : false
}
function updateProfile() {
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES, userForm)
}
function updateTZ(value: string) {
userForm.timezone = value
}
</script> </script>

View File

@ -17,37 +17,25 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { computed, defineComponent, PropType } from 'vue' import { computed, toRefs } from 'vue'
import UserProfileTabs from '@/components/User/UserProfileTabs.vue' import UserProfileTabs from '@/components/User/UserProfileTabs.vue'
import { AUTH_USER_STORE } from '@/store/constants' import { AUTH_USER_STORE } from '@/store/constants'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
export default defineComponent({ interface Props {
name: 'ProfileEdition', user: IUserProfile
components: { tab: string
UserProfileTabs, }
}, const props = defineProps<Props>()
props: {
user: { const store = useStore()
type: Object as PropType<IUserProfile>,
required: true, const { user, tab } = toRefs(props)
}, const tabs = ['PROFILE', 'PICTURE', 'PREFERENCES']
tab: { const loading = computed(
type: String, () => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
required: true, )
},
},
setup() {
const store = useStore()
return {
loading: computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
),
tabs: ['PROFILE', 'PICTURE', 'PREFERENCES'],
}
},
})
</script> </script>

View File

@ -86,8 +86,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ComputedRef, computed, defineComponent, reactive, watch } from 'vue' import {
ComputedRef,
computed,
reactive,
toRefs,
watch,
withDefaults,
} from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants' import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
@ -95,104 +102,90 @@
import { ILoginRegisterFormData } from '@/types/user' import { ILoginRegisterFormData } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
export default defineComponent({ interface Props {
name: 'UserAuthForm', action: string
props: { token?: string
action: { }
type: String, const props = withDefaults(defineProps<Props>(), {
required: true, token: '',
},
token: {
type: String,
default: '',
},
},
setup(props) {
const formData: ILoginRegisterFormData = reactive({
username: '',
email: '',
password: '',
password_conf: '',
})
const route = useRoute()
const store = useStore()
const buttonText: ComputedRef<string> = computed(() =>
getButtonText(props.action)
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
const appConfig: ComputedRef<TAppConfig> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
)
const registration_disabled: ComputedRef<boolean> = computed(
() =>
props.action === 'register' &&
!appConfig.value.is_registration_enabled
)
function getButtonText(action: string): string {
switch (action) {
case 'reset-request':
case 'reset':
return 'buttons.SUBMIT'
default:
return `buttons.${props.action.toUpperCase()}`
}
}
function onSubmit(actionType: string) {
switch (actionType) {
case 'reset':
if (!props.token) {
return store.commit(
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
'user.INVALID_TOKEN'
)
}
return store.dispatch(AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
password: formData.password,
password_conf: formData.password_conf,
token: props.token,
})
case 'reset-request':
return store.dispatch(
AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST,
{
email: formData.email,
}
)
default:
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
actionType,
formData,
redirectUrl: route.query.from,
})
}
}
function resetFormData() {
formData.username = ''
formData.email = ''
formData.password = ''
formData.password_conf = ''
}
watch(
() => route.path,
async () => {
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
resetFormData()
}
)
return {
appConfig,
buttonText,
errorMessages,
formData,
registration_disabled,
onSubmit,
}
},
}) })
const route = useRoute()
const store = useStore()
const { action } = toRefs(props)
const formData: ILoginRegisterFormData = reactive({
username: '',
email: '',
password: '',
password_conf: '',
})
const buttonText: ComputedRef<string> = computed(() =>
getButtonText(props.action)
)
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
const appConfig: ComputedRef<TAppConfig> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
)
const registration_disabled: ComputedRef<boolean> = computed(
() =>
props.action === 'register' && !appConfig.value.is_registration_enabled
)
function getButtonText(action: string): string {
switch (action) {
case 'reset-request':
case 'reset':
return 'buttons.SUBMIT'
default:
return `buttons.${props.action.toUpperCase()}`
}
}
function onSubmit(actionType: string) {
switch (actionType) {
case 'reset':
if (!props.token) {
return store.commit(
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
'user.INVALID_TOKEN'
)
}
return store.dispatch(AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
password: formData.password,
password_conf: formData.password_conf,
token: props.token,
})
case 'reset-request':
return store.dispatch(
AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST,
{
email: formData.email,
}
)
default:
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
actionType,
formData,
redirectUrl: route.query.from,
})
}
}
function resetFormData() {
formData.username = ''
formData.email = ''
formData.password = ''
formData.password_conf = ''
}
watch(
() => route.path,
async () => {
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
resetFormData()
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -12,30 +12,22 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, computed, defineComponent } from 'vue' import { computed } from 'vue'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
import { getApiUrl } from '@/utils' import { getApiUrl } from '@/utils'
export default defineComponent({ interface Props {
name: 'UserPicture', user: IUserProfile
props: { }
user: { const props = defineProps<Props>()
type: Object as PropType<IUserProfile>,
required: true, const authUserPictureUrl = computed(() =>
}, props.user.picture
}, ? `${getApiUrl()}users/${props.user.username}/picture`
setup(props) { : ''
return { )
authUserPictureUrl: computed(() =>
props.user.picture
? `${getApiUrl()}users/${props.user.username}/picture`
: ''
),
}
},
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -18,44 +18,32 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { PropType, defineComponent } from 'vue' import { toRefs, withDefaults } from 'vue'
export default defineComponent({ interface Props {
name: 'UserProfileTabs', tabs: string[]
props: { selectedTab: string
tabs: { edition: boolean
type: Object as PropType<string[]>, disabled?: boolean
required: true, }
}, const props = withDefaults(defineProps<Props>(), {
selectedTab: { disabled: false,
type: String,
required: true,
},
edition: {
type: Boolean,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
},
setup(props) {
function getPath(tab: string) {
switch (tab) {
case 'PICTURE':
return '/profile/edit/picture'
case 'PREFERENCES':
return `/profile${props.edition ? '/edit' : ''}/preferences`
default:
case 'PROFILE':
return `/profile${props.edition ? '/edit' : ''}`
}
}
return { getPath }
},
}) })
const { tabs, selectedTab, disabled } = toRefs(props)
function getPath(tab: string) {
switch (tab) {
case 'PICTURE':
return '/profile/edit/picture'
case 'PREFERENCES':
return `/profile${props.edition ? '/edit' : ''}/preferences`
default:
case 'PROFILE':
return `/profile${props.edition ? '/edit' : ''}`
}
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -17,7 +17,7 @@
</div> </div>
<router-link <router-link
class="workout-title" class="workout-title"
v-if="workout" v-if="workout.id"
:to="{ :to="{
name: 'Workout', name: 'Workout',
params: { workoutId: workout.id }, params: { workoutId: workout.id },
@ -27,7 +27,7 @@
</router-link> </router-link>
<div <div
class="workout-date" class="workout-date"
v-if="workout && user" v-if="workout.workout_date && user"
:title=" :title="
format( format(
getDateWithTZ(workout.workout_date, user.timezone), getDateWithTZ(workout.workout_date, user.timezone),
@ -47,7 +47,7 @@
class="workout-map" class="workout-map"
:class="{ 'no-cursor': !workout }" :class="{ 'no-cursor': !workout }"
@click=" @click="
workout workout.id
? $router.push({ ? $router.push({
name: 'Workout', name: 'Workout',
params: { workoutId: workout.id }, params: { workoutId: workout.id },
@ -66,11 +66,16 @@
class="workout-data" class="workout-data"
:class="{ 'without-gpx': workout && !workout.with_gpx }" :class="{ 'without-gpx': workout && !workout.with_gpx }"
@click=" @click="
$router.push({ name: 'Workout', params: { workoutId: workout.id } }) workout.id
? $router.push({
name: 'Workout',
params: { workoutId: workout.id },
})
: null
" "
> >
<div class="img"> <div class="img">
<SportImage v-if="sport" :sport-label="sport.label" /> <SportImage v-if="sport.label" :sport-label="sport.label" />
</div> </div>
<div class="data"> <div class="data">
<i class="fa fa-clock-o" aria-hidden="true" /> <i class="fa fa-clock-o" aria-hidden="true" />
@ -103,9 +108,9 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { Locale, format, formatDistance } from 'date-fns' import { Locale, format, formatDistance } from 'date-fns'
import { PropType, defineComponent, ComputedRef, computed } from 'vue' import { ComputedRef, computed, toRefs, withDefaults } from 'vue'
import StaticMap from '@/components/Common/StaticMap.vue' import StaticMap from '@/components/Common/StaticMap.vue'
import UserPicture from '@/components/User/UserPicture.vue' import UserPicture from '@/components/User/UserPicture.vue'
@ -116,39 +121,22 @@
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getDateWithTZ } from '@/utils/dates' import { getDateWithTZ } from '@/utils/dates'
export default defineComponent({ interface Props {
name: 'WorkoutCard', user: IUserProfile
components: { workout?: IWorkout
StaticMap, sport?: ISport
UserPicture, }
}, const props = withDefaults(defineProps<Props>(), {
props: { workout: () => ({} as IWorkout),
workout: { sport: () => ({} as ISport),
type: Object as PropType<IWorkout>,
required: false,
},
user: {
type: Object as PropType<IUserProfile>,
required: true,
},
sport: {
type: Object as PropType<ISport>,
required: false,
},
},
setup() {
const store = useStore()
const locale: ComputedRef<Locale> = computed(
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
)
return {
format,
formatDistance,
getDateWithTZ,
locale,
}
},
}) })
const store = useStore()
const { user, workout, sport } = toRefs(props)
const locale: ComputedRef<Locale> = computed(
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -36,9 +36,9 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ChartData, ChartOptions } from 'chart.js' import { ChartData, ChartOptions } from 'chart.js'
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue' import { ComputedRef, computed, ref } from 'vue'
import { LineChart, useLineChart } from 'vue-chart-3' import { LineChart, useLineChart } from 'vue-chart-3'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -50,160 +50,142 @@
} from '@/types/workouts' } from '@/types/workouts'
import { getDatasets } from '@/utils/workouts' import { getDatasets } from '@/utils/workouts'
export default defineComponent({ interface Props {
name: 'WorkoutChart', authUser: IUserProfile
components: { workoutData: IWorkoutData
LineChart, }
}, const props = defineProps<Props>()
props: {
authUser: { const emit = defineEmits(['getCoordinates'])
type: Object as PropType<IUserProfile>,
required: true, const { t } = useI18n()
},
workoutData: { let displayDistance = ref(true)
type: Object as PropType<IWorkoutData>, const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
required: true, getDatasets(props.workoutData.chartData, t)
)
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
labels: displayDistance.value
? datasets.value.distance_labels
: datasets.value.duration_labels,
datasets: JSON.parse(
JSON.stringify([
datasets.value.datasets.speed,
datasets.value.datasets.elevation,
])
),
}))
const coordinates: ComputedRef<TCoordinates[]> = computed(
() => datasets.value.coordinates
)
const options = computed<ChartOptions<'line'>>(() => ({
responsive: true,
maintainAspectRatio: true,
animation: false,
layout: {
padding: {
top: 22,
}, },
}, },
emits: ['getCoordinates'], scales: {
setup(props, { emit }) { [displayDistance.value ? 'xDistance' : 'xDuration']: {
const { t } = useI18n() grid: {
drawOnChartArea: false,
let displayDistance = ref(true) },
const datasets: ComputedRef<IWorkoutChartData> = computed(() => ticks: {
getDatasets(props.workoutData.chartData, t) count: 10,
) callback: function (value) {
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({ return displayDistance.value
labels: displayDistance.value ? Number(value).toFixed(2)
? datasets.value.distance_labels : formatDuration(value)
: datasets.value.duration_labels,
datasets: JSON.parse(
JSON.stringify([
datasets.value.datasets.speed,
datasets.value.datasets.elevation,
])
),
}))
const coordinates: ComputedRef<TCoordinates[]> = computed(
() => datasets.value.coordinates
)
const options = computed<ChartOptions<'line'>>(() => ({
responsive: true,
maintainAspectRatio: true,
animation: false,
layout: {
padding: {
top: 22,
}, },
}, },
scales: { type: 'linear',
[displayDistance.value ? 'xDistance' : 'xDuration']: { bounds: 'data',
grid: { title: {
drawOnChartArea: false, display: true,
}, text: displayDistance.value
ticks: { ? t('workouts.DISTANCE') + ' (km)'
count: 10, : t('workouts.DURATION'),
callback: function (value) {
return displayDistance.value
? Number(value).toFixed(2)
: formatDuration(value)
},
},
type: 'linear',
bounds: 'data',
title: {
display: true,
text: displayDistance.value
? t('workouts.DISTANCE') + ' (km)'
: t('workouts.DURATION'),
},
},
ySpeed: {
grid: {
drawOnChartArea: false,
},
position: 'left',
title: {
display: true,
text: t('workouts.SPEED') + ' (km/h)',
},
},
yElevation: {
beginAtZero: true,
grid: {
drawOnChartArea: false,
},
position: 'right',
title: {
display: true,
text: t('workouts.ELEVATION') + ' (m)',
},
},
}, },
elements: { },
point: { ySpeed: {
pointStyle: 'circle', grid: {
pointRadius: 0, drawOnChartArea: false,
},
}, },
plugins: { position: 'left',
datalabels: { title: {
display: false, display: true,
}, text: t('workouts.SPEED') + ' (km/h)',
tooltip: {
interaction: {
intersect: false,
mode: 'index',
},
callbacks: {
label: function (context) {
const label = ` ${context.dataset.label}: ${context.formattedValue}`
return context.dataset.yAxisID === 'yElevation'
? label + ' m'
: label + ' km/h'
},
title: function (tooltipItems) {
if (tooltipItems.length > 0) {
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
}
return tooltipItems.length === 0
? ''
: displayDistance.value
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
: `${t('workouts.DURATION')}: ${formatDuration(
tooltipItems[0].label.replace(',', '')
)}`
},
},
},
}, },
})) },
yElevation: {
function updateDisplayDistance() { beginAtZero: true,
displayDistance.value = !displayDistance.value grid: {
} drawOnChartArea: false,
function formatDuration(duration: string | number): string { },
return new Date(+duration * 1000).toISOString().substr(11, 8) position: 'right',
} title: {
function emitCoordinates(coordinates: TCoordinates) { display: true,
emit('getCoordinates', coordinates) text: t('workouts.ELEVATION') + ' (m)',
} },
function emitEmptyCoordinates() { },
emitCoordinates({ latitude: null, longitude: null })
}
const { lineChartProps } = useLineChart({
chartData,
options,
})
return {
displayDistance,
lineChartProps,
emitEmptyCoordinates,
updateDisplayDistance,
}
}, },
elements: {
point: {
pointStyle: 'circle',
pointRadius: 0,
},
},
plugins: {
datalabels: {
display: false,
},
tooltip: {
interaction: {
intersect: false,
mode: 'index',
},
callbacks: {
label: function (context) {
const label = ` ${context.dataset.label}: ${context.formattedValue}`
return context.dataset.yAxisID === 'yElevation'
? label + ' m'
: label + ' km/h'
},
title: function (tooltipItems) {
if (tooltipItems.length > 0) {
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
}
return tooltipItems.length === 0
? ''
: displayDistance.value
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
: `${t('workouts.DURATION')}: ${formatDuration(
tooltipItems[0].label.replace(',', '')
)}`
},
},
},
},
}))
const { lineChartProps } = useLineChart({
chartData,
options,
}) })
function updateDisplayDistance() {
displayDistance.value = !displayDistance.value
}
function formatDuration(duration: string | number): string {
return new Date(+duration * 1000).toISOString().substr(11, 8)
}
function emitCoordinates(coordinates: TCoordinates) {
emit('getCoordinates', coordinates)
}
function emitEmptyCoordinates() {
emitCoordinates({ latitude: null, longitude: null })
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

Some files were not shown because too many files have changed in this diff Show More