commit
675561d654
@ -22,7 +22,7 @@ class TestLogin:
|
||||
assert inputs[1].get_attribute('id') == 'password'
|
||||
assert inputs[1].get_attribute('type') == 'password'
|
||||
|
||||
button = selenium.find_element(By.TAG_NAME, 'button')
|
||||
button = selenium.find_elements(By.TAG_NAME, 'button')[-1]
|
||||
assert button.get_attribute('type') == 'submit'
|
||||
assert 'Log in' in button.text
|
||||
|
||||
@ -47,4 +47,7 @@ class TestLogin:
|
||||
assert 'Statistics' in nav
|
||||
assert 'Add a workout' in nav
|
||||
assert user['username'] in nav
|
||||
assert 'Logout' in nav
|
||||
logout_button = selenium.find_elements(By.CLASS_NAME, 'logout-button')[
|
||||
0
|
||||
]
|
||||
assert logout_button
|
||||
|
@ -6,14 +6,19 @@ from .utils import register_valid_user
|
||||
class TestLogout:
|
||||
def test_user_can_log_out(self, selenium):
|
||||
user = register_valid_user(selenium)
|
||||
user_menu = selenium.find_element(By.CLASS_NAME, 'nav-items-user-menu')
|
||||
logout_link = user_menu.find_elements(By.CLASS_NAME, 'nav-item')[2]
|
||||
logout_button = selenium.find_elements(By.CLASS_NAME, 'logout-button')[
|
||||
0
|
||||
]
|
||||
|
||||
logout_link.click()
|
||||
logout_button.click()
|
||||
modal = selenium.find_element(By.ID, 'modal')
|
||||
confirm_button = modal.find_elements(By.CLASS_NAME, 'confirm')[0]
|
||||
confirm_button.click()
|
||||
selenium.implicitly_wait(1)
|
||||
|
||||
nav = selenium.find_element(By.ID, 'nav').text
|
||||
assert 'Register' in nav
|
||||
assert 'Login' in nav
|
||||
assert user['username'] not in nav
|
||||
assert 'Logout' not in nav
|
||||
buttons = selenium.find_elements(By.CLASS_NAME, 'logout-button')
|
||||
assert buttons == []
|
||||
|
@ -35,7 +35,7 @@ class TestRegistration:
|
||||
assert form_infos[1].text == 'Enter a valid email address.'
|
||||
assert form_infos[2].text == 'At least 8 characters required.'
|
||||
|
||||
button = selenium.find_element(By.TAG_NAME, 'button')
|
||||
button = selenium.find_elements(By.TAG_NAME, 'button')[-1]
|
||||
assert button.get_attribute('type') == 'submit'
|
||||
assert 'Register' in button.text
|
||||
|
||||
@ -84,7 +84,11 @@ class TestRegistration:
|
||||
register(selenium, user)
|
||||
|
||||
assert selenium.current_url == URL
|
||||
errors = selenium.find_element(By.CLASS_NAME, 'error-message').text
|
||||
errors = (
|
||||
selenium.find_element(By.ID, 'user-form')
|
||||
.find_element(By.CLASS_NAME, 'error-message')
|
||||
.text
|
||||
)
|
||||
assert 'Sorry, that username is already taken.' in errors
|
||||
|
||||
def test_user_does_not_return_error_if_email_is_already_taken(
|
||||
|
@ -30,7 +30,7 @@ class TestWorkout:
|
||||
)
|
||||
selenium.find_element(By.NAME, 'workout-distance').send_keys('10')
|
||||
|
||||
confirm_button = selenium.find_element(By.CLASS_NAME, 'confirm')
|
||||
confirm_button = selenium.find_elements(By.CLASS_NAME, 'confirm')[-1]
|
||||
confirm_button.click()
|
||||
|
||||
WebDriverWait(selenium, 10).until(
|
||||
|
12
e2e/utils.py
12
e2e/utils.py
@ -34,7 +34,7 @@ def register(selenium, user):
|
||||
password.send_keys(user.get('password'))
|
||||
accepted_policy = selenium.find_element(By.ID, 'accepted_policy')
|
||||
accepted_policy.click()
|
||||
submit_button = selenium.find_element(By.TAG_NAME, 'button')
|
||||
submit_button = selenium.find_elements(By.TAG_NAME, 'button')[-1]
|
||||
submit_button.click()
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ def login(selenium, user):
|
||||
email.send_keys(user.get('email'))
|
||||
password = selenium.find_element(By.ID, 'password')
|
||||
password.send_keys(user.get('password'))
|
||||
submit_button = selenium.find_element(By.TAG_NAME, 'button')
|
||||
submit_button = selenium.find_elements(By.TAG_NAME, 'button')[-1]
|
||||
submit_button.click()
|
||||
|
||||
|
||||
@ -65,8 +65,12 @@ def register_valid_user(selenium):
|
||||
def register_valid_user_and_logout(selenium):
|
||||
user = register_valid_user(selenium)
|
||||
user_menu = selenium.find_element(By.CLASS_NAME, 'nav-items-user-menu')
|
||||
logout_link = user_menu.find_elements(By.CLASS_NAME, 'nav-item')[2]
|
||||
logout_link.click()
|
||||
logout_button = user_menu.find_elements(By.CLASS_NAME, 'logout-button')[0]
|
||||
logout_button.click()
|
||||
modal = selenium.find_element(By.ID, 'modal')
|
||||
confirm_button = modal.find_elements(By.CLASS_NAME, 'confirm')[0]
|
||||
confirm_button.click()
|
||||
selenium.implicitly_wait(1)
|
||||
return user
|
||||
|
||||
|
||||
|
2
fittrackee/dist/index.html
vendored
2
fittrackee/dist/index.html
vendored
@ -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><script defer="defer" src="/static/js/chunk-vendors.b1590c25.js"></script><script defer="defer" src="/static/js/app.d394b3e5.js"></script><link href="/static/css/app.ce91f57c.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></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><script defer="defer" src="/static/js/chunk-vendors.1d7b137c.js"></script><script defer="defer" src="/static/js/app.6bf88dea.js"></script><link href="/static/css/app.33d241fd.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></body></html>
|
2
fittrackee/dist/service-worker.js
vendored
2
fittrackee/dist/service-worker.js
vendored
File diff suppressed because one or more lines are too long
2
fittrackee/dist/service-worker.js.map
vendored
2
fittrackee/dist/service-worker.js.map
vendored
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/app.33d241fd.css
vendored
Normal file
1
fittrackee/dist/static/css/app.33d241fd.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/app.ce91f57c.css
vendored
1
fittrackee/dist/static/css/app.ce91f57c.css
vendored
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
.chart-menu[data-v-22d55de2]{display:flex}.chart-menu .chart-arrow[data-v-22d55de2],.chart-menu .time-frames[data-v-22d55de2]{flex-grow:1;text-align:center}.chart-menu .chart-arrow[data-v-22d55de2]{cursor:pointer}.sports-menu{display:flex;flex-wrap:wrap;padding:10px}.sports-menu label{display:flex;align-items:center;font-size:.9em;font-weight:400;min-width:120px;padding:10px}@media screen and (max-width:1000px){.sports-menu label{min-width:100px}}@media screen and (max-width:500px){.sports-menu label{min-width:20px}.sports-menu label .sport-label{display:none}}.sports-menu .sport-img{padding:3px;width:20px;height:20px}#user-statistics.stats-disabled[data-v-30799d13]{opacity:.3;pointer-events:none}#user-statistics[data-v-30799d13] .chart-radio{justify-content:space-around;padding:30px 10px 10px 10px}#statistics[data-v-2e341d4e]{display:flex;width:100%}#statistics .container[data-v-2e341d4e]{display:flex;flex-direction:column;width:100%}
|
1
fittrackee/dist/static/css/statistics.8a97ec6e.css
vendored
Normal file
1
fittrackee/dist/static/css/statistics.8a97ec6e.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.chart-menu[data-v-361ef577]{display:flex;align-items:center}.chart-menu .chart-arrow[data-v-361ef577],.chart-menu .time-frames[data-v-361ef577]{flex-grow:1;text-align:center}.chart-menu .chart-arrow[data-v-361ef577]{cursor:pointer}.sports-menu{display:flex;flex-wrap:wrap;padding:10px}.sports-menu label{display:flex;align-items:center;font-size:.9em;font-weight:400;min-width:120px;padding:10px}@media screen and (max-width:1000px){.sports-menu label{min-width:100px}}@media screen and (max-width:500px){.sports-menu label{min-width:20px}.sports-menu label .sport-label{display:none}}.sports-menu .sport-img{padding:3px;width:20px;height:20px}#user-statistics.stats-disabled[data-v-47c262da]{opacity:.3;pointer-events:none}#user-statistics[data-v-47c262da] .chart-radio{justify-content:space-around;padding:30px 10px 10px 10px}#statistics[data-v-19ce09a2]{display:flex;width:100%}#statistics .container[data-v-19ce09a2]{display:flex;flex-direction:column;width:100%}
|
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/workouts.8b705dca.css
vendored
Normal file
1
fittrackee/dist/static/css/workouts.8b705dca.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/app.6bf88dea.js
vendored
Normal file
2
fittrackee/dist/static/js/app.6bf88dea.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/app.6bf88dea.js.map
vendored
Normal file
1
fittrackee/dist/static/js/app.6bf88dea.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/app.d394b3e5.js
vendored
2
fittrackee/dist/static/js/app.d394b3e5.js
vendored
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
@ -1,2 +1,2 @@
|
||||
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[845],{4264:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),c=r(5801),i=r(9917);const l={key:0,id:"account-confirmation",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"AccountConfirmationView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,i.o)(),d=(0,n.Fl)((()=>_.getters[c.SY.GETTERS.ERROR_MESSAGES])),S=(0,n.Fl)((()=>t.query.token));function m(){S.value?_.dispatch(c.YN.ACTIONS.CONFIRM_ACCOUNT,{token:S.value}):r.push("/")}return(0,n.wF)((()=>m())),(0,n.Ah)((()=>_.commit(c.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(e,t)=>{const r=(0,n.up)("router-link");return(0,a.SU)(d)?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n.Wm)(r,{class:"links",to:"/account-confirmation/resend"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("buttons.ACCOUNT-CONFIRMATION-RESEND"))+"? ",1)])),_:1})])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-785df978"]]);var m=S},8160:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),c=r(5801),i=r(9917);const l={key:0,id:"email-update",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"EmailUpdateView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,i.o)(),d=(0,n.Fl)((()=>_.getters[c.YN.GETTERS.AUTH_USER_PROFILE])),S=(0,n.Fl)((()=>_.getters[c.YN.GETTERS.IS_AUTHENTICATED])),m=(0,n.Fl)((()=>_.getters[c.SY.GETTERS.ERROR_MESSAGES])),p=(0,n.Fl)((()=>t.query.token));function R(){p.value?_.dispatch(c.YN.ACTIONS.CONFIRM_EMAIL,{token:p.value,refreshUser:S.value}):r.push("/")}return(0,n.wF)((()=>R())),(0,n.Ah)((()=>_.commit(c.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(0,n.YP)((()=>m.value),(e=>{d.value.username&&e&&r.push("/")})),(e,t)=>{const r=(0,n.up)("router-link"),u=(0,n.up)("i18n-t");return(0,a.SU)(m)&&!(0,a.SU)(d).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n._)("span",null,[(0,n.Wm)(u,{keypath:"user.PROFILE.ERRORED_EMAIL_UPDATE"},{default:(0,n.w5)((()=>[(0,n.Wm)(r,{to:"/login"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("user.LOG_IN")),1)])),_:1})])),_:1})])])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-8c2ec9ce"]]);var m=S},3537:function(e,t,r){r.r(t),r.d(t,{default:function(){return d}});var n=r(6252),a=r(2262),s=r(5801),u=r(9917);const o=e=>((0,n.dD)("data-v-0c3c0394"),e=e(),(0,n.Cn)(),e),c={key:0,id:"profile",class:"view"},i=o((()=>(0,n._)("div",{id:"bottom"},null,-1)));var l=(0,n.aZ)({__name:"ProfileView",setup(e){const t=(0,u.o)(),r=(0,n.Fl)((()=>t.getters[s.YN.GETTERS.AUTH_USER_PROFILE]));return(e,t)=>{const s=(0,n.up)("router-view");return(0,a.SU)(r).username?((0,n.wg)(),(0,n.iD)("div",c,[(0,n.Wm)(s,{user:(0,a.SU)(r)},null,8,["user"]),i])):(0,n.kq)("",!0)}}}),E=r(3744);const _=(0,E.Z)(l,[["__scopeId","data-v-0c3c0394"]]);var d=_},9453:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var n=r(6252),a=r(2262),s=r(2201),u=r(2179),o=r(1585),c=r(5801),i=r(9917);const l={key:0,id:"user",class:"view"},E={class:"box"};var _=(0,n.aZ)({__name:"UserView",props:{fromAdmin:{type:Boolean}},setup(e){const t=e,{fromAdmin:r}=(0,a.BK)(t),_=(0,s.yj)(),d=(0,i.o)(),S=(0,n.Fl)((()=>d.getters[c.RT.GETTERS.USER]));return(0,n.wF)((()=>{_.params.username&&"string"===typeof _.params.username&&d.dispatch(c.RT.ACTIONS.GET_USER,_.params.username)})),(0,n.Jd)((()=>{d.dispatch(c.RT.ACTIONS.EMPTY_USER)})),(e,t)=>(0,a.SU)(S).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(u.Z,{user:(0,a.SU)(S)},null,8,["user"]),(0,n._)("div",E,[(0,n.Wm)(o.Z,{user:(0,a.SU)(S),"from-admin":(0,a.SU)(r)},null,8,["user","from-admin"])])])):(0,n.kq)("",!0)}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-af7007f4"]]);var m=S}}]);
|
||||
//# sourceMappingURL=profile.d4857496.js.map
|
||||
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[845],{4264:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),c=r(5801),i=r(9917);const l={key:0,id:"account-confirmation",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"AccountConfirmationView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,i.o)(),d=(0,n.Fl)((()=>_.getters[c.SY.GETTERS.ERROR_MESSAGES])),S=(0,n.Fl)((()=>t.query.token));function m(){S.value?_.dispatch(c.YN.ACTIONS.CONFIRM_ACCOUNT,{token:S.value}):r.push("/")}return(0,n.wF)((()=>m())),(0,n.Ah)((()=>_.commit(c.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(e,t)=>{const r=(0,n.up)("router-link");return(0,a.SU)(d)?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n.Wm)(r,{class:"links",to:"/account-confirmation/resend"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("buttons.ACCOUNT-CONFIRMATION-RESEND"))+"? ",1)])),_:1})])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-785df978"]]);var m=S},8160:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),c=r(5801),i=r(9917);const l={key:0,id:"email-update",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"EmailUpdateView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,i.o)(),d=(0,n.Fl)((()=>_.getters[c.YN.GETTERS.AUTH_USER_PROFILE])),S=(0,n.Fl)((()=>_.getters[c.YN.GETTERS.IS_AUTHENTICATED])),m=(0,n.Fl)((()=>_.getters[c.SY.GETTERS.ERROR_MESSAGES])),p=(0,n.Fl)((()=>t.query.token));function R(){p.value?_.dispatch(c.YN.ACTIONS.CONFIRM_EMAIL,{token:p.value,refreshUser:S.value}):r.push("/")}return(0,n.wF)((()=>R())),(0,n.Ah)((()=>_.commit(c.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(0,n.YP)((()=>m.value),(e=>{d.value.username&&e&&r.push("/")})),(e,t)=>{const r=(0,n.up)("router-link"),u=(0,n.up)("i18n-t");return(0,a.SU)(m)&&!(0,a.SU)(d).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n._)("span",null,[(0,n.Wm)(u,{keypath:"user.PROFILE.ERRORED_EMAIL_UPDATE"},{default:(0,n.w5)((()=>[(0,n.Wm)(r,{to:"/login"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("user.LOG_IN")),1)])),_:1})])),_:1})])])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-8c2ec9ce"]]);var m=S},3537:function(e,t,r){r.r(t),r.d(t,{default:function(){return d}});var n=r(6252),a=r(2262),s=r(5801),u=r(9917);const o=e=>((0,n.dD)("data-v-0c3c0394"),e=e(),(0,n.Cn)(),e),c={key:0,id:"profile",class:"view"},i=o((()=>(0,n._)("div",{id:"bottom"},null,-1)));var l=(0,n.aZ)({__name:"ProfileView",setup(e){const t=(0,u.o)(),r=(0,n.Fl)((()=>t.getters[s.YN.GETTERS.AUTH_USER_PROFILE]));return(e,t)=>{const s=(0,n.up)("router-view");return(0,a.SU)(r).username?((0,n.wg)(),(0,n.iD)("div",c,[(0,n.Wm)(s,{user:(0,a.SU)(r)},null,8,["user"]),i])):(0,n.kq)("",!0)}}}),E=r(3744);const _=(0,E.Z)(l,[["__scopeId","data-v-0c3c0394"]]);var d=_},9453:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var n=r(6252),a=r(2262),s=r(2201),u=r(2179),o=r(8732),c=r(5801),i=r(9917);const l={key:0,id:"user",class:"view"},E={class:"box"};var _=(0,n.aZ)({__name:"UserView",props:{fromAdmin:{type:Boolean}},setup(e){const t=e,{fromAdmin:r}=(0,a.BK)(t),_=(0,s.yj)(),d=(0,i.o)(),S=(0,n.Fl)((()=>d.getters[c.RT.GETTERS.USER]));return(0,n.wF)((()=>{_.params.username&&"string"===typeof _.params.username&&d.dispatch(c.RT.ACTIONS.GET_USER,_.params.username)})),(0,n.Jd)((()=>{d.dispatch(c.RT.ACTIONS.EMPTY_USER)})),(e,t)=>(0,a.SU)(S).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(u.Z,{user:(0,a.SU)(S)},null,8,["user"]),(0,n._)("div",E,[(0,n.Wm)(o.Z,{user:(0,a.SU)(S),"from-admin":(0,a.SU)(r)},null,8,["user","from-admin"])])])):(0,n.kq)("",!0)}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-af7007f4"]]);var m=S}}]);
|
||||
//# sourceMappingURL=profile.168ed5aa.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
@ -1,2 +0,0 @@
|
||||
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7885:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var a=t(6252),r=t(2262),l=t(3577),o=(t(7658),t(9150)),n=t(436);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-30799d13"]]);var Z=F,x=t(5630),D=t(5801),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
|
||||
//# sourceMappingURL=statistics.ec64386f.js.map
|
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/statistics.f9b8289f.js
vendored
Normal file
2
fittrackee/dist/static/js/statistics.f9b8289f.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/statistics.f9b8289f.js.map
vendored
Normal file
1
fittrackee/dist/static/js/statistics.f9b8289f.js.map
vendored
Normal file
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
2
fittrackee/dist/static/js/workouts.e3c8eeab.js
vendored
Normal file
2
fittrackee/dist/static/js/workouts.e3c8eeab.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/workouts.e3c8eeab.js.map
vendored
Normal file
1
fittrackee/dist/static/js/workouts.e3c8eeab.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,24 +1,26 @@
|
||||
<template>
|
||||
<div id="top" />
|
||||
<NavBar @menuInteraction="updateHideScrollBar" />
|
||||
<div v-if="appLoading" class="app-container">
|
||||
<div class="app-loading">
|
||||
<Loader />
|
||||
<main>
|
||||
<div v-if="appLoading" class="app-container">
|
||||
<div class="app-loading">
|
||||
<Loader />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="app-container" :class="{ 'hide-scroll': hideScrollBar }">
|
||||
<router-view v-if="appConfig" />
|
||||
<NoConfig v-else />
|
||||
</div>
|
||||
<div class="container scroll">
|
||||
<div
|
||||
class="scroll-button"
|
||||
:class="{ 'display-button': displayScrollButton }"
|
||||
@click="scrollToTop"
|
||||
>
|
||||
<i class="fa fa-chevron-up" aria-hidden="true"></i>
|
||||
<div v-else class="app-container" :class="{ 'hide-scroll': hideScrollBar }">
|
||||
<router-view v-if="appConfig" />
|
||||
<NoConfig v-else />
|
||||
</div>
|
||||
</div>
|
||||
<div class="container scroll">
|
||||
<div
|
||||
class="scroll-button"
|
||||
:class="{ 'display-button': displayScrollButton }"
|
||||
@click="scrollToTop"
|
||||
>
|
||||
<i class="fa fa-chevron-up" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer
|
||||
v-if="appConfig"
|
||||
:version="appConfig ? appConfig.version : ''"
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="admin-menu description-list">
|
||||
<dl>
|
||||
<dt>
|
||||
<router-link to="/admin/application">
|
||||
<router-link id="adminLink" to="/admin/application">
|
||||
{{ $t('admin.APPLICATION') }}
|
||||
</router-link>
|
||||
</dt>
|
||||
@ -54,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { capitalize, toRefs, withDefaults } from 'vue'
|
||||
import { capitalize, onMounted, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import AppStatsCards from '@/components/Administration/AppStatsCards.vue'
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
@ -69,6 +69,13 @@
|
||||
})
|
||||
|
||||
const { appConfig, appStatistics } = toRefs(props)
|
||||
|
||||
onMounted(() => {
|
||||
const applicationLink = document.getElementById('adminLink')
|
||||
if (applicationLink) {
|
||||
applicationLink.focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -61,13 +61,15 @@
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.PROFILE.REGISTRATION_DATE') }}
|
||||
</span>
|
||||
{{
|
||||
formatDate(
|
||||
user.created_at,
|
||||
authUser.timezone,
|
||||
authUser.date_format
|
||||
)
|
||||
}}
|
||||
<time>
|
||||
{{
|
||||
formatDate(
|
||||
user.created_at,
|
||||
authUser.timezone,
|
||||
authUser.date_format
|
||||
)
|
||||
}}
|
||||
</time>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="cell-heading">
|
||||
|
@ -1,15 +1,23 @@
|
||||
<template>
|
||||
<div class="dropdown-wrapper">
|
||||
<div class="dropdown-selected" @click="toggleDropdown">
|
||||
<button
|
||||
:aria-label="buttonLabel"
|
||||
:aria-expanded="isOpen"
|
||||
class="dropdown-selector transparent"
|
||||
@click="toggleDropdown"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</button>
|
||||
<ul class="dropdown-list" v-if="isOpen">
|
||||
<li
|
||||
class="dropdown-item"
|
||||
:class="{ selected: option.value === selected }"
|
||||
v-for="(option, index) in dropdownOptions"
|
||||
:key="index"
|
||||
tabindex="0"
|
||||
@click="updateSelected(option)"
|
||||
@keydown.enter="updateSelected(option)"
|
||||
role="button"
|
||||
>
|
||||
{{ option.label }}
|
||||
</li>
|
||||
@ -25,6 +33,7 @@
|
||||
interface Props {
|
||||
options: TDropdownOptions
|
||||
selected: string
|
||||
buttonLabel: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@ -51,36 +60,39 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dropdown-list {
|
||||
list-style-type: none;
|
||||
background-color: #ffffff;
|
||||
padding: 0 !important;
|
||||
margin-top: 5px;
|
||||
margin-left: -20px !important;
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
border: solid 1px lightgrey;
|
||||
box-shadow: 2px 2px 5px lightgrey;
|
||||
width: auto !important;
|
||||
|
||||
li {
|
||||
padding: 3px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
font-weight: bold;
|
||||
@import '~@/scss/vars.scss';
|
||||
.dropdown-wrapper {
|
||||
.dropdown-selector {
|
||||
margin: 0;
|
||||
padding: $default-padding * 0.5;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
content: ' ✔';
|
||||
}
|
||||
.dropdown-list {
|
||||
list-style-type: none;
|
||||
background-color: #ffffff;
|
||||
padding: 0 !important;
|
||||
margin-top: 5px;
|
||||
margin-left: -20px !important;
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
border: solid 1px lightgrey;
|
||||
box-shadow: 2px 2px 5px lightgrey;
|
||||
width: auto !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dropdown-hover-color);
|
||||
.dropdown-item {
|
||||
padding: 3px 12px;
|
||||
&.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
content: ' ✔';
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dropdown-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="modal">
|
||||
<div id="modal" role="dialog">
|
||||
<div class="custom-modal">
|
||||
<Card>
|
||||
<template #title>
|
||||
@ -21,7 +21,12 @@
|
||||
>
|
||||
{{ $t('buttons.YES') }}
|
||||
</button>
|
||||
<button class="cancel" @click="emit('cancelAction')">
|
||||
<button
|
||||
:tabindex="0"
|
||||
:id="`${name}-cancel-button`"
|
||||
class="cancel"
|
||||
@click="emit('cancelAction')"
|
||||
>
|
||||
{{ $t(`buttons.${errorMessages ? 'CANCEL' : 'NO'}`) }}
|
||||
</button>
|
||||
</div>
|
||||
@ -41,9 +46,11 @@
|
||||
title: string
|
||||
message: string
|
||||
strongMessage?: string | null
|
||||
name?: string | null
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
strongMessage: () => null,
|
||||
name: 'modal',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['cancelAction', 'confirmAction'])
|
||||
|
@ -7,6 +7,7 @@
|
||||
class="page-link"
|
||||
:to="{ path, query: getQuery(pagination.page, -1) }"
|
||||
:disabled="!pagination.has_prev"
|
||||
:tabindex="pagination.has_prev ? 0 : -1"
|
||||
>
|
||||
<slot @click="pagination.has_next ? navigate : null">
|
||||
{{ $t('api.PAGINATION.PREVIOUS') }}
|
||||
@ -35,6 +36,7 @@
|
||||
class="page-link"
|
||||
:to="{ path, query: getQuery(pagination.page, 1) }"
|
||||
:disabled="!pagination.has_next"
|
||||
:tabindex="pagination.has_next ? 0 : -1"
|
||||
>
|
||||
<slot @click="pagination.has_next ? navigate : null">
|
||||
{{ $t('api.PAGINATION.NEXT') }}
|
||||
|
@ -11,13 +11,15 @@
|
||||
@input="updatePassword"
|
||||
@invalid="invalidPassword"
|
||||
/>
|
||||
<div class="show-password" @click="togglePassword">
|
||||
{{ $t(`user.${showPassword ? 'HIDE' : 'SHOW'}_PASSWORD`) }}
|
||||
<i
|
||||
class="fa"
|
||||
:class="`fa-eye${showPassword ? '-slash' : ''}`"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="show-password">
|
||||
<button class="transparent" @click.prevent="togglePassword" type="button">
|
||||
{{ $t(`user.${showPassword ? 'HIDE' : 'SHOW'}_PASSWORD`) }}
|
||||
<i
|
||||
class="fa"
|
||||
:class="`fa-eye${showPassword ? '-slash' : ''}`"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="checkStrength" class="form-info">
|
||||
<i class="fa fa-info-circle" aria-hidden="true" />
|
||||
@ -71,6 +73,7 @@
|
||||
(newPassword) => {
|
||||
if (newPassword === '') {
|
||||
passwordValue.value = ''
|
||||
showPassword.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -84,12 +87,15 @@
|
||||
flex-direction: column;
|
||||
|
||||
.show-password {
|
||||
font-style: italic;
|
||||
font-size: 0.85em;
|
||||
text-align: right;
|
||||
margin-top: -0.75 * $default-margin;
|
||||
padding-right: $default-padding;
|
||||
cursor: pointer;
|
||||
margin-top: -0.5 * $default-margin;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
button {
|
||||
font-style: italic;
|
||||
font-size: 0.85em;
|
||||
padding: $default-padding * 0.5 $default-padding;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -9,6 +9,7 @@
|
||||
min="0"
|
||||
max="4"
|
||||
step="1"
|
||||
:tabindex="-1"
|
||||
/>
|
||||
<div v-if="passwordStrength" class="password-strength-details">
|
||||
<span class="password-strength-value">
|
||||
|
@ -10,6 +10,7 @@
|
||||
type="radio"
|
||||
name="total_distance"
|
||||
:checked="displayedData === 'total_distance'"
|
||||
:disabled="isDisabled"
|
||||
@click="updateDisplayData"
|
||||
/>
|
||||
{{ $t('workouts.DISTANCE') }}
|
||||
@ -19,6 +20,7 @@
|
||||
type="radio"
|
||||
name="total_duration"
|
||||
:checked="displayedData === 'total_duration'"
|
||||
:disabled="isDisabled"
|
||||
@click="updateDisplayData"
|
||||
/>
|
||||
{{ $t('workouts.DURATION') }}
|
||||
@ -28,6 +30,7 @@
|
||||
type="radio"
|
||||
name="nb_workouts"
|
||||
:checked="displayedData === 'nb_workouts'"
|
||||
:disabled="isDisabled"
|
||||
@click="updateDisplayData"
|
||||
/>
|
||||
{{ $t('workouts.WORKOUT', 2) }}
|
||||
@ -37,6 +40,7 @@
|
||||
type="radio"
|
||||
name="average_speed"
|
||||
:checked="displayedData === 'average_speed'"
|
||||
:disabled="isDisabled"
|
||||
@click="updateDisplayData"
|
||||
/>
|
||||
{{ $t('workouts.AVERAGE_SPEED') }}
|
||||
@ -46,6 +50,7 @@
|
||||
type="radio"
|
||||
name="total_ascent"
|
||||
:checked="displayedData === 'total_ascent'"
|
||||
:disabled="isDisabled"
|
||||
@click="updateDisplayData"
|
||||
/>
|
||||
{{ $t('workouts.ASCENT') }}
|
||||
@ -55,6 +60,7 @@
|
||||
type="radio"
|
||||
name="total_descent"
|
||||
:checked="displayedData === 'total_descent'"
|
||||
:disabled="isDisabled"
|
||||
@click="updateDisplayData"
|
||||
/>
|
||||
{{ $t('workouts.DESCENT') }}
|
||||
@ -130,6 +136,10 @@
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
|
@ -22,7 +22,7 @@
|
||||
params: { workoutId: record.workout_id },
|
||||
}"
|
||||
>
|
||||
{{ record.workout_date }}
|
||||
<time>{{ record.workout_date }}</time>
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="footer">
|
||||
<footer id="footer">
|
||||
<div class="footer-items">
|
||||
<div class="footer-item">
|
||||
<strong>FitTrackee</strong>
|
||||
@ -22,7 +22,7 @@
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -1,22 +1,31 @@
|
||||
<template>
|
||||
<div id="nav">
|
||||
<header id="nav">
|
||||
<Modal
|
||||
v-show="displayModal"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="$t('user.LOGOUT_CONFIRMATION')"
|
||||
@confirmAction="logout"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
@keydown.esc="updateDisplayModal(false)"
|
||||
/>
|
||||
<div class="nav-container">
|
||||
<div class="nav-app-name">
|
||||
<div class="nav-item app-name" @click="$router.push('/')">
|
||||
FitTrackee
|
||||
</div>
|
||||
<router-link class="nav-item app-name" to="/">FitTrackee</router-link>
|
||||
</div>
|
||||
<div class="nav-icon-open" :class="{ 'menu-open': isMenuOpen }">
|
||||
<i class="fa fa-bars hamburger-icon" @click="openMenu()"></i>
|
||||
<button class="menu-button transparent" @click="openMenu()">
|
||||
<i class="fa fa-bars hamburger-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nav-items" :class="{ 'menu-open': isMenuOpen }">
|
||||
<div class="nav-items-close">
|
||||
<div class="app-name">FitTrackee</div>
|
||||
<i
|
||||
class="fa fa-close close-icon nav-item"
|
||||
:class="{ 'menu-closed': !isMenuOpen }"
|
||||
@click="closeMenu()"
|
||||
/>
|
||||
<button class="menu-button transparent" @click="closeMenu()">
|
||||
<i
|
||||
class="fa fa-close close-icon nav-item"
|
||||
:class="{ 'menu-closed': !isMenuOpen }"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nav-items-app-menu" @click="closeMenu()">
|
||||
<div class="nav-items-group" v-if="isAuthenticated">
|
||||
@ -50,9 +59,14 @@
|
||||
<router-link class="nav-item" to="/profile" @click="closeMenu">
|
||||
{{ authUser.username }}
|
||||
</router-link>
|
||||
<div class="nav-item nav-link" @click="logout">
|
||||
{{ $t('user.LOGOUT') }}
|
||||
</div>
|
||||
<button
|
||||
class="logout-button transparent"
|
||||
@click="updateDisplayModal(true)"
|
||||
:aria-label="$t('user.LOGOUT')"
|
||||
>
|
||||
<i class="fa fa-sign-out logout-fa" aria-hidden="true" />
|
||||
<span class="logout-text">{{ $t('user.LOGOUT') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nav-items-group" v-else>
|
||||
<router-link class="nav-item" to="/login" @click="closeMenu">
|
||||
@ -68,17 +82,18 @@
|
||||
:options="availableLanguages"
|
||||
:selected="language"
|
||||
@selected="updateLanguage"
|
||||
:buttonLabel="$t('user.REGISTER')"
|
||||
>
|
||||
<i class="fa fa-language"></i>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, ref, capitalize } from 'vue'
|
||||
import { ComputedRef, Ref, computed, ref, capitalize } from 'vue'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -100,7 +115,8 @@
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
const isMenuOpen = ref(false)
|
||||
const isMenuOpen: Ref<boolean> = ref(false)
|
||||
const displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
function openMenu() {
|
||||
isMenuOpen.value = true
|
||||
@ -118,6 +134,16 @@
|
||||
}
|
||||
function logout() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
|
||||
displayModal.value = false
|
||||
}
|
||||
function updateDisplayModal(display: boolean) {
|
||||
displayModal.value = display
|
||||
if (display) {
|
||||
const button = document.getElementById('modal-cancel-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -154,10 +180,7 @@
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.fa {
|
||||
@ -172,12 +195,15 @@
|
||||
.close-icon {
|
||||
display: none;
|
||||
}
|
||||
.menu-button {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-items {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
line-height: 1.8em;
|
||||
line-height: 2em;
|
||||
width: 100%;
|
||||
|
||||
.nav-items-close {
|
||||
@ -196,11 +222,15 @@
|
||||
}
|
||||
.nav-item {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep(.dropdown-list) {
|
||||
z-index: 1000;
|
||||
margin-left: -160px !important;
|
||||
width: 180px !important;
|
||||
height: 28px;
|
||||
&.dropdown-wrapper {
|
||||
padding: 0;
|
||||
margin-left: 2px;
|
||||
::v-deep(.dropdown-list) {
|
||||
z-index: 1000;
|
||||
margin-left: -150px !important;
|
||||
width: 180px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,6 +256,16 @@
|
||||
.nav-separator {
|
||||
display: none;
|
||||
}
|
||||
.logout-button {
|
||||
padding: $default-padding * 0.5 $default-padding * 0.75;
|
||||
margin-left: 2px;
|
||||
.logout-fa {
|
||||
display: block;
|
||||
}
|
||||
.logout-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $medium-limit) {
|
||||
@ -286,14 +326,31 @@
|
||||
.nav-items-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.logout-button {
|
||||
padding: $default-padding $default-padding $default-padding
|
||||
$default-padding * 2.4;
|
||||
color: var(--app-a-color);
|
||||
text-align: left;
|
||||
.logout-fa {
|
||||
display: none;
|
||||
}
|
||||
.logout-text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 7px 25px;
|
||||
|
||||
::v-deep(.dropdown-list) {
|
||||
margin-left: initial !important;
|
||||
width: auto !important;
|
||||
&.dropdown-wrapper {
|
||||
padding-left: $default-padding * 2;
|
||||
::v-deep(.dropdown-list) {
|
||||
margin-left: initial !important;
|
||||
width: auto !important;
|
||||
height: 200px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,10 +365,6 @@
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-items-user-menu :nth-child(1) {
|
||||
order: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -4,19 +4,20 @@
|
||||
{{ capitalize($t('privacy_policy.TITLE')) }}
|
||||
</h1>
|
||||
<p class="last-update">
|
||||
{{ $t('privacy_policy.LAST_UPDATE')}}: {{ private_policy_date }}
|
||||
{{ $t('privacy_policy.LAST_UPDATE') }}:
|
||||
<time>{{ privatePolicyDate }}</time>
|
||||
</p>
|
||||
<template v-if="appConfig.privacy_policy">
|
||||
<div
|
||||
v-html="snarkdown(linkifyAndClean(appConfig.privacy_policy))"
|
||||
/>
|
||||
<div v-html="snarkdown(linkifyAndClean(appConfig.privacy_policy))" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-for="paragraph in paragraphs" :key="paragraph">
|
||||
<h2>
|
||||
{{ $t(`privacy_policy.CONTENT.${paragraph}.TITLE`)}}
|
||||
{{ $t(`privacy_policy.CONTENT.${paragraph}.TITLE`) }}
|
||||
</h2>
|
||||
<p v-html="snarkdown($t(`privacy_policy.CONTENT.${paragraph}.CONTENT`))" />
|
||||
<p
|
||||
v-html="snarkdown($t(`privacy_policy.CONTENT.${paragraph}.CONTENT`))"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
@ -34,7 +35,7 @@
|
||||
import { linkifyAndClean } from '@/utils/inputs'
|
||||
|
||||
const store = useStore()
|
||||
const fittrackee_private_policy_date = 'Sun, 26 Feb 2023 17:00:00 GMT'
|
||||
const fittrackeePrivatePolicyDate = 'Sun, 26 Feb 2023 17:00:00 GMT'
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
@ -46,19 +47,24 @@
|
||||
)
|
||||
const dateFormat = computed(() => getDateFormat())
|
||||
const timezone = computed(() => getTimezone())
|
||||
const private_policy_date = computed(() => getPolicyDate())
|
||||
const privatePolicyDate = computed(() => getPolicyDate())
|
||||
const paragraphs = [
|
||||
'DATA_COLLECTED', 'INFORMATION_USAGE', 'INFORMATION_PROTECTION',
|
||||
'INFORMATION_DISCLOSURE', 'SITE_USAGE_BY_CHILDREN', 'YOUR_CONSENT',
|
||||
'ACCOUNT_DELETION', 'CHANGES_TO_OUR_PRIVACY_POLICY'
|
||||
'DATA_COLLECTED',
|
||||
'INFORMATION_USAGE',
|
||||
'INFORMATION_PROTECTION',
|
||||
'INFORMATION_DISCLOSURE',
|
||||
'SITE_USAGE_BY_CHILDREN',
|
||||
'YOUR_CONSENT',
|
||||
'ACCOUNT_DELETION',
|
||||
'CHANGES_TO_OUR_PRIVACY_POLICY',
|
||||
]
|
||||
|
||||
function getTimezone() {
|
||||
return authUser.value.timezone
|
||||
? authUser.value.timezone
|
||||
: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
: 'Europe/Paris'
|
||||
? authUser.value.timezone
|
||||
: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
: 'Europe/Paris'
|
||||
}
|
||||
function getDateFormat() {
|
||||
return dateStringFormats[language.value]
|
||||
@ -66,14 +72,13 @@
|
||||
function getPolicyDate() {
|
||||
return formatDate(
|
||||
appConfig.value.privacy_policy && appConfig.value.privacy_policy_date
|
||||
? `${appConfig.value.privacy_policy_date}`
|
||||
: fittrackee_private_policy_date,
|
||||
? `${appConfig.value.privacy_policy_date}`
|
||||
: fittrackeePrivatePolicyDate,
|
||||
timezone.value,
|
||||
dateFormat.value,
|
||||
false,
|
||||
)
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="chart-menu">
|
||||
<div class="chart-arrow">
|
||||
<i
|
||||
class="fa fa-chevron-left"
|
||||
aria-hidden="true"
|
||||
@click="emit('arrowClick', true)"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="chart-arrow transparent"
|
||||
@click="emit('arrowClick', true)"
|
||||
@keydown.enter="emit('arrowClick', true)"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</button>
|
||||
<div class="time-frames custom-checkboxes-group">
|
||||
<div class="time-frames-checkboxes custom-checkboxes">
|
||||
<div
|
||||
@ -21,24 +22,38 @@
|
||||
:name="frame"
|
||||
:checked="selectedTimeFrame === frame"
|
||||
@input="onUpdateTimeFrame(frame)"
|
||||
:disabled="isDisabled"
|
||||
/>
|
||||
<span>{{ $t(`statistics.TIME_FRAMES.${frame}`) }}</span>
|
||||
<span
|
||||
:id="`frame-${frame}`"
|
||||
:tabindex="isDisabled ? -1 : 0"
|
||||
role="button"
|
||||
@keydown.enter="onUpdateTimeFrame(frame)"
|
||||
>
|
||||
{{ $t(`statistics.TIME_FRAMES.${frame}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-arrow">
|
||||
<i
|
||||
class="fa fa-chevron-right"
|
||||
aria-hidden="true"
|
||||
@click="emit('arrowClick', false)"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="chart-arrow transparent"
|
||||
@click="emit('arrowClick', false)"
|
||||
@keydown.enter="emit('arrowClick', false)"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onMounted, ref, toRefs } from 'vue'
|
||||
interface Props {
|
||||
isDisabled: boolean
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const { isDisabled } = toRefs(props)
|
||||
|
||||
const emit = defineEmits(['arrowClick', 'timeFrameUpdate'])
|
||||
|
||||
@ -49,11 +64,21 @@
|
||||
selectedTimeFrame.value = timeFrame
|
||||
emit('timeFrameUpdate', timeFrame)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!isDisabled.value) {
|
||||
const input = document.getElementById('frame-month')
|
||||
if (input) {
|
||||
input.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.chart-arrow,
|
||||
.time-frames {
|
||||
|
@ -3,6 +3,7 @@
|
||||
<StatsMenu
|
||||
@timeFrameUpdate="updateTimeFrame"
|
||||
@arrowClick="handleOnClickArrows"
|
||||
:isDisabled="isDisabled"
|
||||
/>
|
||||
<StatChart
|
||||
:sports="sports"
|
||||
@ -10,6 +11,7 @@
|
||||
:chartParams="chartParams"
|
||||
:displayed-sport-ids="selectedSportIds"
|
||||
:fullStats="true"
|
||||
:isDisabled="isDisabled"
|
||||
/>
|
||||
<SportsMenu
|
||||
:selected-sport-ids="selectedSportIds"
|
||||
@ -35,6 +37,7 @@
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IAuthUserProfile
|
||||
isDisabled: boolean
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
<div id="user-infos" class="description-list">
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
name="user"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="
|
||||
displayModal === 'delete'
|
||||
@ -15,6 +16,7 @@
|
||||
: resetUserPassword(user.username)
|
||||
"
|
||||
@cancelAction="updateDisplayModal('')"
|
||||
@keydown.esc="updateDisplayModal('')"
|
||||
/>
|
||||
<div class="info-box success-message" v-if="isSuccess">
|
||||
{{
|
||||
@ -58,13 +60,17 @@
|
||||
<div v-else>
|
||||
<dl>
|
||||
<dt>{{ $t('user.PROFILE.REGISTRATION_DATE') }}:</dt>
|
||||
<dd>{{ registrationDate }}</dd>
|
||||
<dd>
|
||||
<time>{{ registrationDate }}</time>
|
||||
</dd>
|
||||
<dt>{{ $t('user.PROFILE.FIRST_NAME') }}:</dt>
|
||||
<dd>{{ user.first_name }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.LAST_NAME') }}:</dt>
|
||||
<dd>{{ user.last_name }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.BIRTH_DATE') }}:</dt>
|
||||
<dd>{{ birthDate }}</dd>
|
||||
<dd>
|
||||
<time v-if="birthDate">{{ birthDate }}</time>
|
||||
</dd>
|
||||
<dt>{{ $t('user.PROFILE.LOCATION') }}:</dt>
|
||||
<dd>{{ user.location }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.BIO') }}:</dt>
|
||||
@ -186,6 +192,10 @@
|
||||
function updateDisplayModal(value: string) {
|
||||
displayModal.value = value
|
||||
if (value !== '') {
|
||||
const button = document.getElementById('user-cancel-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
store.commit(USERS_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@
|
||||
<div id="user-infos-edition">
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
name="account"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="$t('user.CONFIRM_ACCOUNT_DELETION')"
|
||||
@confirmAction="deleteAccount(user.username)"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
@keydown.esc="updateDisplayModal(false)"
|
||||
/>
|
||||
<div class="profile-form form-box">
|
||||
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
|
||||
@ -62,7 +64,11 @@
|
||||
<button class="danger" @click.prevent="updateDisplayModal(true)">
|
||||
{{ $t('buttons.DELETE_MY_ACCOUNT') }}
|
||||
</button>
|
||||
<button class="confirm" v-if="canRequestExport()" @click.prevent="requestExport">
|
||||
<button
|
||||
class="confirm"
|
||||
v-if="canRequestExport()"
|
||||
@click.prevent="requestExport"
|
||||
>
|
||||
{{ $t('buttons.REQUEST_DATA_EXPORT') }}
|
||||
</button>
|
||||
</div>
|
||||
@ -73,22 +79,22 @@
|
||||
{{ $t('user.EXPORT_REQUEST.ONLY_ONE_EXPORT_PER_DAY') }}
|
||||
</span>
|
||||
<div v-if="exportRequest" class="data-export-archive">
|
||||
{{$t('user.EXPORT_REQUEST.DATA_EXPORT')}}
|
||||
{{ $t('user.EXPORT_REQUEST.DATA_EXPORT') }}
|
||||
({{ exportRequestDate }}):
|
||||
<span
|
||||
v-if="exportRequest.status=== 'successful'"
|
||||
v-if="exportRequest.status === 'successful'"
|
||||
class="archive-link"
|
||||
@click.prevent="downloadArchive(exportRequest.file_name)"
|
||||
>
|
||||
<i class="fa fa-download" aria-hidden="true" />
|
||||
{{ $t("user.EXPORT_REQUEST.DOWNLOAD_ARCHIVE") }}
|
||||
{{ $t('user.EXPORT_REQUEST.DOWNLOAD_ARCHIVE') }}
|
||||
({{ getReadableFileSize(exportRequest.file_size) }})
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t(`user.EXPORT_REQUEST.STATUS.${exportRequest.status}`)}}
|
||||
{{ $t(`user.EXPORT_REQUEST.STATUS.${exportRequest.status}`) }}
|
||||
</span>
|
||||
<span v-if="generatingLink">
|
||||
{{ $t(`user.EXPORT_REQUEST.GENERATING_LINK`)}}
|
||||
{{ $t(`user.EXPORT_REQUEST.GENERATING_LINK`) }}
|
||||
<i class="fa fa-spinner fa-pulse" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
@ -111,11 +117,15 @@
|
||||
onUnmounted,
|
||||
} from 'vue'
|
||||
|
||||
import authApi from "@/api/authApi";
|
||||
import authApi from '@/api/authApi'
|
||||
import PasswordInput from '@/components/Common/PasswordInput.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import {IAuthUserProfile, IUserAccountPayload, IExportRequest} from '@/types/user'
|
||||
import {
|
||||
IAuthUserProfile,
|
||||
IUserAccountPayload,
|
||||
IExportRequest,
|
||||
} from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { formatDate } from '@/utils/dates'
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
@ -150,8 +160,8 @@
|
||||
const exportRequest: ComputedRef<IExportRequest | null> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.EXPORT_REQUEST]
|
||||
)
|
||||
const exportRequestDate: ComputedRef<string | null> = computed(
|
||||
() => getExportRequestDate()
|
||||
const exportRequestDate: ComputedRef<string | null> = computed(() =>
|
||||
getExportRequestDate()
|
||||
)
|
||||
const generatingLink: Ref<boolean> = ref(false)
|
||||
|
||||
@ -175,19 +185,22 @@
|
||||
userForm.new_password = new_password
|
||||
}
|
||||
function getExportRequestDate() {
|
||||
return exportRequest.value ? formatDate(
|
||||
exportRequest.value.created_at,
|
||||
user.value.timezone,
|
||||
user.value.date_format,
|
||||
true,
|
||||
null, true
|
||||
) : null
|
||||
return exportRequest.value
|
||||
? formatDate(
|
||||
exportRequest.value.created_at,
|
||||
user.value.timezone,
|
||||
user.value.date_format,
|
||||
true,
|
||||
null,
|
||||
true
|
||||
)
|
||||
: null
|
||||
}
|
||||
|
||||
function canRequestExport() {
|
||||
return exportRequestDate.value
|
||||
? isBefore(new Date(exportRequestDate.value), subDays(new Date(), 1))
|
||||
: true
|
||||
? isBefore(new Date(exportRequestDate.value), subDays(new Date(), 1))
|
||||
: true
|
||||
}
|
||||
function updateProfile() {
|
||||
const payload: IUserAccountPayload = {
|
||||
@ -202,6 +215,12 @@
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
if (displayModal.value) {
|
||||
const button = document.getElementById('account-cancel-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
function deleteAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
@ -225,7 +244,7 @@
|
||||
document.body.appendChild(archive_link)
|
||||
archive_link.click()
|
||||
})
|
||||
.finally(() => generatingLink.value = false)
|
||||
.finally(() => (generatingLink.value = false))
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -284,8 +303,8 @@
|
||||
.data-export {
|
||||
padding: $default-padding 0;
|
||||
.data-export-archive {
|
||||
padding-top: $default-padding*2;
|
||||
font-size: .9em;
|
||||
padding-top: $default-padding * 2;
|
||||
font-size: 0.9em;
|
||||
|
||||
.archive-link {
|
||||
color: var(--app-a-color);
|
||||
|
@ -2,10 +2,12 @@
|
||||
<div id="oauth2-app" class="description-list">
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
name="app"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="$t(messageToDisplay)"
|
||||
@confirmAction="confirmAction(client.id)"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
@keydown.esc="updateDisplayModal(false)"
|
||||
/>
|
||||
<div v-if="client && client.client_id">
|
||||
<div
|
||||
@ -49,13 +51,15 @@
|
||||
</dd>
|
||||
<dt>{{ capitalize($t('oauth2.APP.ISSUE_AT')) }}:</dt>
|
||||
<dd>
|
||||
{{
|
||||
formatDate(
|
||||
client.issued_at,
|
||||
authUser.timezone,
|
||||
authUser.date_format
|
||||
)
|
||||
}}
|
||||
<time>
|
||||
{{
|
||||
formatDate(
|
||||
client.issued_at,
|
||||
authUser.timezone,
|
||||
authUser.date_format
|
||||
)
|
||||
}}
|
||||
</time>
|
||||
</dd>
|
||||
<dt>{{ $t('oauth2.APP.NAME') }}:</dt>
|
||||
<dd>{{ client.name }}</dd>
|
||||
@ -176,6 +180,11 @@
|
||||
displayModal.value = value
|
||||
if (!value) {
|
||||
messageToDisplay.value = null
|
||||
} else {
|
||||
const button = document.getElementById('app-cancel-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
function confirmAction(clientId: number) {
|
||||
|
@ -8,13 +8,15 @@
|
||||
</router-link>
|
||||
<span class="app-issued-at">
|
||||
{{ $t('oauth2.APP.ISSUE_AT') }}
|
||||
{{
|
||||
formatDate(
|
||||
client.issued_at,
|
||||
authUser.timezone,
|
||||
authUser.date_format
|
||||
)
|
||||
}}
|
||||
<time>
|
||||
{{
|
||||
formatDate(
|
||||
client.issued_at,
|
||||
authUser.timezone,
|
||||
authUser.date_format
|
||||
)
|
||||
}}
|
||||
</time>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -11,7 +11,14 @@
|
||||
:disabled="disabled"
|
||||
@input="$router.push(getPath(tab))"
|
||||
/>
|
||||
<span>{{ $t(`user.PROFILE.TABS.${tab}`) }}</span>
|
||||
<span
|
||||
:id="`tab-${tab}`"
|
||||
:tabindex="0"
|
||||
role="button"
|
||||
@keydown.enter="$router.push(getPath(tab))"
|
||||
>
|
||||
{{ $t(`user.PROFILE.TABS.${tab}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -19,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
import { onMounted, toRefs, withDefaults } from 'vue'
|
||||
|
||||
interface Props {
|
||||
tabs: string[]
|
||||
@ -33,6 +40,13 @@
|
||||
|
||||
const { tabs, selectedTab, disabled } = toRefs(props)
|
||||
|
||||
onMounted(() => {
|
||||
const input = document.getElementById(`tab-${tabs.value[0]}`)
|
||||
if (input) {
|
||||
input.focus()
|
||||
}
|
||||
})
|
||||
|
||||
function getPath(tab: string) {
|
||||
switch (tab) {
|
||||
case 'ACCOUNT':
|
||||
|
@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div id="workout-card-title">
|
||||
<div
|
||||
class="workout-previous workout-arrow"
|
||||
<button
|
||||
class="workout-previous workout-arrow transparent"
|
||||
:class="{ inactive: !workoutObject.previousUrl }"
|
||||
:disabled="!workoutObject.previousUrl"
|
||||
:title="
|
||||
workoutObject.previousUrl
|
||||
? $t(`workouts.PREVIOUS_${workoutObject.type}`)
|
||||
@ -15,33 +16,40 @@
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
<div class="workout-card-title">
|
||||
<SportImage :sport-label="sport.label" :color="sport.color" />
|
||||
<div class="workout-title-date">
|
||||
<div class="workout-title" v-if="workoutObject.type === 'WORKOUT'">
|
||||
<span>{{ workoutObject.title }}</span>
|
||||
<i
|
||||
class="fa fa-edit"
|
||||
aria-hidden="true"
|
||||
<button
|
||||
class="transparent icon-button"
|
||||
@click="
|
||||
$router.push({
|
||||
name: 'EditWorkout',
|
||||
params: { workoutId: workoutObject.workoutId },
|
||||
})
|
||||
"
|
||||
/>
|
||||
<i
|
||||
:aria-label="$t(`workouts.EDIT_WORKOUT`)"
|
||||
>
|
||||
<i class="fa fa-edit" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="workoutObject.with_gpx"
|
||||
class="fa fa-download"
|
||||
aria-hidden="true"
|
||||
class="transparent icon-button"
|
||||
@click.prevent="downloadGpx(workoutObject.workoutId)"
|
||||
/>
|
||||
<i
|
||||
class="fa fa-trash"
|
||||
aria-hidden="true"
|
||||
@click="emit('displayModal', true)"
|
||||
/>
|
||||
:aria-label="$t(`workouts.DOWNLOAD_WORKOUT`)"
|
||||
>
|
||||
<i class="fa fa-download" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
id="delete-workout-button"
|
||||
class="transparent icon-button"
|
||||
@click="displayDeleteModal"
|
||||
:aria-label="$t(`workouts.DELETE_WORKOUT`)"
|
||||
>
|
||||
<i class="fa fa-trash" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="workout-title" v-else>
|
||||
{{ workoutObject.title }}
|
||||
@ -53,14 +61,15 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="workout-date">
|
||||
{{ workoutObject.workoutDate }} -
|
||||
{{ workoutObject.workoutTime }}
|
||||
<time>
|
||||
{{ workoutObject.workoutDate }} - {{ workoutObject.workoutTime }}
|
||||
</time>
|
||||
<span class="workout-link">
|
||||
<router-link
|
||||
v-if="workoutObject.type === 'SEGMENT'"
|
||||
:to="{
|
||||
name: 'Workout',
|
||||
params: { workoutId: workoutObject.workoutId },
|
||||
params: { workoutId: workoutObject.workoutId }
|
||||
}"
|
||||
>
|
||||
> {{ $t('workouts.BACK_TO_WORKOUT') }}
|
||||
@ -69,9 +78,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="workout-next workout-arrow"
|
||||
<button
|
||||
class="workout-next workout-arrow transparent"
|
||||
:class="{ inactive: !workoutObject.nextUrl }"
|
||||
:disabled="!workoutObject.nextUrl"
|
||||
:title="
|
||||
workoutObject.nextUrl
|
||||
? $t(`workouts.NEXT_${workoutObject.type}`)
|
||||
@ -82,7 +92,7 @@
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -119,6 +129,10 @@
|
||||
gpxLink.click()
|
||||
})
|
||||
}
|
||||
function displayDeleteModal(event: Event & { target: HTMLInputElement }) {
|
||||
event.target.blur()
|
||||
emit('displayModal', true)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -166,9 +180,13 @@
|
||||
}
|
||||
|
||||
.fa {
|
||||
cursor: pointer;
|
||||
padding: 0 $default-padding * 0.3;
|
||||
}
|
||||
.icon-button {
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-limit) {
|
||||
|
@ -18,12 +18,24 @@
|
||||
@ready="fitBounds(bounds)"
|
||||
>
|
||||
<LControlLayers />
|
||||
<LControl position="topleft" class="map-control" @click="resetZoom">
|
||||
<LControl
|
||||
position="topleft"
|
||||
class="map-control"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('workouts.RESET_ZOOM')"
|
||||
@click="resetZoom"
|
||||
>
|
||||
<i class="fa fa-refresh" aria-hidden="true" />
|
||||
</LControl>
|
||||
<LControl
|
||||
position="topleft"
|
||||
class="map-control"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="
|
||||
$t(`workouts.${isFullscreen ? 'EXIT' : 'VIEW'}_FULLSCREEN`)
|
||||
"
|
||||
@click="toggleFullscreen"
|
||||
>
|
||||
<i
|
||||
|
@ -2,10 +2,12 @@
|
||||
<div class="workout-detail">
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
name="workout"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="$t('workouts.WORKOUT_DELETION_CONFIRMATION')"
|
||||
@confirmAction="deleteWorkout(workoutObject.workoutId)"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
@cancelAction="cancelDelete"
|
||||
@keydown.esc="cancelDelete"
|
||||
/>
|
||||
<Card>
|
||||
<template #title>
|
||||
@ -35,6 +37,7 @@
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
nextTick,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
@ -161,18 +164,50 @@
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
if (displayModal.value) {
|
||||
nextTick(() => {
|
||||
const button = document.getElementById('workout-cancel-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
function cancelDelete() {
|
||||
updateDisplayModal(false)
|
||||
const button = document.getElementById('delete-workout-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
}
|
||||
function deleteWorkout(workoutId: string) {
|
||||
updateDisplayModal(false)
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.DELETE_WORKOUT, {
|
||||
workoutId: workoutId,
|
||||
})
|
||||
}
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.segmentId,
|
||||
async (newSegmentId) => {
|
||||
if (newSegmentId) {
|
||||
segmentId.value = +newSegmentId
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => route.params.workoutId,
|
||||
async (workoutId) => {
|
||||
if (workoutId) {
|
||||
displayModal.value = false
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -352,8 +352,15 @@
|
||||
const payloadErrorMessages: Ref<string[]> = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
let element
|
||||
if (props.workout.id) {
|
||||
formatWorkoutForm(props.workout)
|
||||
element = document.getElementById('sport')
|
||||
} else {
|
||||
element = document.getElementById('withGpx')
|
||||
}
|
||||
if (element) {
|
||||
element.focus()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="workouts-filters">
|
||||
<div class="box">
|
||||
<form v-on:submit.prevent="onSubmit" class="form">
|
||||
<form v-on:submit.prevent="onFilter" class="form">
|
||||
<div class="form-all-items">
|
||||
<div class="form-items-group">
|
||||
<div class="form-item">
|
||||
<label> {{ $t('workouts.FROM') }}: </label>
|
||||
<input
|
||||
id="from"
|
||||
name="from"
|
||||
type="date"
|
||||
:value="$route.query.from"
|
||||
@ -31,6 +32,7 @@
|
||||
name="sport_id"
|
||||
:value="$route.query.sport_id"
|
||||
@change="handleFilterChange"
|
||||
@keyup.enter="onFilter"
|
||||
>
|
||||
<option value="" />
|
||||
<option
|
||||
@ -54,9 +56,9 @@
|
||||
@change="handleFilterChange"
|
||||
placeholder=""
|
||||
type="text"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -71,7 +73,7 @@
|
||||
step="0.1"
|
||||
:value="$route.query.distance_from"
|
||||
@change="handleFilterChange"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
<span>{{ $t('workouts.TO') }}</span>
|
||||
<input
|
||||
@ -81,7 +83,7 @@
|
||||
step="0.1"
|
||||
:value="$route.query.distance_to"
|
||||
@change="handleFilterChange"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -98,7 +100,7 @@
|
||||
pattern="^([0-9]*[0-9]):([0-5][0-9])$"
|
||||
placeholder="hh:mm"
|
||||
type="text"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
<span>{{ $t('workouts.TO') }}</span>
|
||||
<input
|
||||
@ -108,7 +110,7 @@
|
||||
pattern="^([0-9]*[0-9]):([0-5][0-9])$"
|
||||
placeholder="hh:mm"
|
||||
type="text"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -125,7 +127,7 @@
|
||||
@change="handleFilterChange"
|
||||
step="0.1"
|
||||
type="number"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
<span>{{ $t('workouts.TO') }}</span>
|
||||
<input
|
||||
@ -135,7 +137,7 @@
|
||||
@change="handleFilterChange"
|
||||
step="0.1"
|
||||
type="number"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -153,7 +155,7 @@
|
||||
@change="handleFilterChange"
|
||||
step="0.1"
|
||||
type="number"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
<span>{{ $t('workouts.TO') }}</span>
|
||||
<input
|
||||
@ -163,7 +165,7 @@
|
||||
@change="handleFilterChange"
|
||||
step="0.1"
|
||||
type="number"
|
||||
@keyup.enter="submit"
|
||||
@keyup.enter="onFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -184,7 +186,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, toRefs, watch } from 'vue'
|
||||
import { ComputedRef, computed, toRefs, watch, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { LocationQuery, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
@ -215,6 +217,13 @@
|
||||
)
|
||||
let params: LocationQuery = Object.assign({}, route.query)
|
||||
|
||||
onMounted(() => {
|
||||
const filter = document.getElementById('from')
|
||||
if (filter) {
|
||||
filter.focus()
|
||||
}
|
||||
})
|
||||
|
||||
function handleFilterChange(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.value === '') {
|
||||
delete params[event.target.name]
|
||||
@ -317,7 +326,8 @@
|
||||
height: 100%;
|
||||
|
||||
.form-item {
|
||||
label, span {
|
||||
label,
|
||||
span {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@ -395,7 +405,6 @@
|
||||
}
|
||||
.form {
|
||||
.form-all-items {
|
||||
|
||||
.form-items-group {
|
||||
.form-item-title {
|
||||
padding-top: $default-padding;
|
||||
|
@ -83,13 +83,15 @@
|
||||
<span class="cell-heading">
|
||||
{{ $t('workouts.DATE') }}
|
||||
</span>
|
||||
{{
|
||||
formatDate(
|
||||
workout.workout_date,
|
||||
user.timezone,
|
||||
user.date_format
|
||||
)
|
||||
}}
|
||||
<time>
|
||||
{{
|
||||
formatDate(
|
||||
workout.workout_date,
|
||||
user.timezone,
|
||||
user.date_format
|
||||
)
|
||||
}}
|
||||
</time>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="cell-heading">
|
||||
|
@ -59,7 +59,8 @@
|
||||
"TABLE": {
|
||||
"ADD_ADMIN_RIGHTS": "Add admin rights",
|
||||
"REMOVE_ADMIN_RIGHTS": "Remove admin rights"
|
||||
}
|
||||
},
|
||||
"TITLE": "Administration - Users"
|
||||
},
|
||||
"USER_EMAIL_UPDATE_SUCCESSFUL": "The email address has been updated."
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"ACCOUNT_CONFIRMATION": "Account confirmation",
|
||||
"ACCOUNT_CONFIRMATION_NOT_RECEIVED": "Didn't received instructions?",
|
||||
"ACCOUNT_CONFIRMATION_SENT": "Check your email. A new confirmation email has been sent to the address provided.",
|
||||
"ADMIN": "Admin",
|
||||
@ -7,6 +8,7 @@
|
||||
"CURRENT_PASSWORD": "Current password",
|
||||
"EMAIL": "Email",
|
||||
"EMAIL_INFO": "Enter a valid email address.",
|
||||
"EMAIL_UPDATE": "Email update",
|
||||
"ENTER_PASSWORD": "Enter a password",
|
||||
"EXPORT_REQUEST": {
|
||||
"DATA_EXPORT": "Data export",
|
||||
@ -26,6 +28,7 @@
|
||||
"LAST_PRIVACY_POLICY_TO_VALIDATE": "The privacy policy has been updated, please {0} it before proceeding.",
|
||||
"LOGIN": "Login",
|
||||
"LOGOUT": "Logout",
|
||||
"LOGOUT_CONFIRMATION": "Are you sure you want to log out?",
|
||||
"LOG_IN": "log in",
|
||||
"NEW_PASSWORD": "New password",
|
||||
"NO_USERS_FOUND": "No users found.",
|
||||
|
@ -6,13 +6,16 @@
|
||||
"AVE_SPEED": "ave. speed",
|
||||
"BACK_TO_WORKOUT": "back to workout",
|
||||
"DATE": "date",
|
||||
"DELETE_WORKOUT": "Delete the workout",
|
||||
"DESCENT": "descent",
|
||||
"DISPLAY_FILTERS": "display filters",
|
||||
"DISTANCE": "distance",
|
||||
"DOWNLOAD_WORKOUT": "Download the workout",
|
||||
"DURATION": "duration",
|
||||
"EDIT_WORKOUT": "Edit the workout",
|
||||
"ELEVATION": "elevation",
|
||||
"END": "end",
|
||||
"EXIT_FULLSCREEN": "Exit Fullscreen",
|
||||
"FROM": "from",
|
||||
"GPX_FILE": ".gpx file",
|
||||
"HIDE_FILTERS": "hide filters",
|
||||
@ -50,6 +53,7 @@
|
||||
"RECORD_LD": "Longest duration",
|
||||
"RECORD_MS": "Max. speed",
|
||||
"REMAINING_CHARS": "remaining characters",
|
||||
"RESET_ZOOM": "Reset zoom",
|
||||
"SEGMENT": "segment | segments",
|
||||
"SPEED": "speed",
|
||||
"SPORT": "sport | sports",
|
||||
@ -60,6 +64,7 @@
|
||||
"TO": "to",
|
||||
"TOTAL_DURATION": "total duration",
|
||||
"UPLOAD_FIRST_WORKOUT": "Upload one!",
|
||||
"VIEW_FULLSCREEN": "View Fullscreen",
|
||||
"WEATHER": {
|
||||
"DARK_SKY": {
|
||||
"clear-day": "clear day",
|
||||
|
@ -59,7 +59,8 @@
|
||||
"TABLE": {
|
||||
"ADD_ADMIN_RIGHTS": "Ajouter les droits d'admin",
|
||||
"REMOVE_ADMIN_RIGHTS": "Retirer les droits d'admin"
|
||||
}
|
||||
},
|
||||
"TITLE": "Administration - Utilisateurs"
|
||||
},
|
||||
"USER_EMAIL_UPDATE_SUCCESSFUL": "L'adresse email a été mise à jour."
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"ACCOUNT_CONFIRMATION": "Confirmation du compte",
|
||||
"ACCOUNT_CONFIRMATION_NOT_RECEIVED": "Vous n'avez pas reçu les instructions ?",
|
||||
"ACCOUNT_CONFIRMATION_SENT": "Vérifiez vos courriels. Un nouveau courriel de confirmation a été envoyé à l'adresse électronique fournie.",
|
||||
"ADMIN": "Admin",
|
||||
@ -7,6 +8,7 @@
|
||||
"CURRENT_PASSWORD": "Mot de passe actuel",
|
||||
"EMAIL": "Courriel",
|
||||
"EMAIL_INFO": "Saisissez une adresse électronique valide.",
|
||||
"EMAIL_UPDATE": "Mise à jour de l'adresse électronique",
|
||||
"ENTER_PASSWORD": "Saisissez un mot de passe",
|
||||
"EXPORT_REQUEST": {
|
||||
"DATA_EXPORT": "Export des données",
|
||||
@ -26,6 +28,7 @@
|
||||
"LAST_PRIVACY_POLICY_TO_VALIDATE": "La politique de confidentialité a été mise à jour. Veuillez l'{0} avant de poursuivre.",
|
||||
"LOGIN": "Se connecter",
|
||||
"LOGOUT": "Se déconnecter",
|
||||
"LOGOUT_CONFIRMATION": "Etes-vous sûr de vouloir vous déconnecter ?",
|
||||
"LOG_IN": "connecter",
|
||||
"NEW_PASSWORD": "Nouveau mot de passe",
|
||||
"NO_USERS_FOUND": "Aucun utilisateur trouvé.",
|
||||
|
@ -6,13 +6,16 @@
|
||||
"AVE_SPEED": "vitesse moy.",
|
||||
"BACK_TO_WORKOUT": "revenir à la séance",
|
||||
"DATE": "date",
|
||||
"DELETE_WORKOUT": "Supprimer la séance",
|
||||
"DESCENT": "dénivelé négatif",
|
||||
"DISPLAY_FILTERS": "afficher les filtres",
|
||||
"DISTANCE": "distance",
|
||||
"DOWNLOAD_WORKOUT": "Télécharger la séance",
|
||||
"DURATION": "durée",
|
||||
"EDIT_WORKOUT": "Modifier la séance",
|
||||
"ELEVATION": "altitude",
|
||||
"END": "fin",
|
||||
"EXIT_FULLSCREEN": "Sortir du plein-écran",
|
||||
"FROM": "à partir de",
|
||||
"GPX_FILE": "fichier .gpx",
|
||||
"HIDE_FILTERS": "masquer les filtres",
|
||||
@ -50,6 +53,7 @@
|
||||
"RECORD_LD": "Durée la + longue",
|
||||
"RECORD_MS": "Vitesse max.",
|
||||
"REMAINING_CHARS": "nombre de caractères restants ",
|
||||
"RESET_ZOOM": "Réinitialiser le zoom",
|
||||
"SEGMENT": "segment | segments",
|
||||
"SPEED": "vitesse",
|
||||
"SPORT": "sport | sports",
|
||||
@ -60,6 +64,7 @@
|
||||
"TO": "jusqu'au",
|
||||
"TOTAL_DURATION": "durée totale",
|
||||
"UPLOAD_FIRST_WORKOUT": "Ajoutez votre première séance !",
|
||||
"VIEW_FULLSCREEN": "Afficher en plein-écran",
|
||||
"WEATHER": {
|
||||
"DARK_SKY": {
|
||||
"clear-day": "ensoleillé",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { capitalize } from 'vue'
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
|
||||
import AdminApplication from '@/components/Administration/AdminApplication.vue'
|
||||
@ -19,6 +20,7 @@ import UserApps from '@/components/User/UserApps/index.vue'
|
||||
import UserApp from '@/components/User/UserApps/UserApp.vue'
|
||||
import UserAppsList from '@/components/User/UserApps/UserAppsList.vue'
|
||||
import UserSportPreferences from '@/components/User/UserSportPreferences.vue'
|
||||
import createI18n from '@/i18n'
|
||||
import store from '@/store'
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import AboutView from '@/views/AboutView.vue'
|
||||
@ -27,6 +29,8 @@ import NotFoundView from '@/views/NotFoundView.vue'
|
||||
import PrivacyPolicyView from '@/views/PrivacyPolicyView.vue'
|
||||
import LoginOrRegister from '@/views/user/LoginOrRegister.vue'
|
||||
|
||||
const { t } = createI18n.global
|
||||
|
||||
const getTabFromPath = (path: string): string => {
|
||||
const regex = /(\/profile)(\/edit)*(\/*)/
|
||||
const tag = path.replace(regex, '').toUpperCase()
|
||||
@ -38,18 +42,27 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '/',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard,
|
||||
meta: {
|
||||
title: 'dashboard.DASHBOARD',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: LoginOrRegister,
|
||||
props: { action: 'login' },
|
||||
meta: {
|
||||
title: 'user.LOGIN',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: LoginOrRegister,
|
||||
props: { action: 'register' },
|
||||
meta: {
|
||||
title: 'user.REGISTER',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/account-confirmation',
|
||||
@ -58,6 +71,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
import(
|
||||
/* webpackChunkName: 'profile' */ '@/views/user/AccountConfirmationView.vue'
|
||||
),
|
||||
meta: {
|
||||
title: 'user.ACCOUNT_CONFIRMATION',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/account-confirmation/resend',
|
||||
@ -67,6 +83,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/AccountConfirmationResendView.vue'
|
||||
),
|
||||
props: { action: 'account-confirmation-resend' },
|
||||
meta: {
|
||||
title: 'buttons.ACCOUNT-CONFIRMATION-RESEND',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/account-confirmation/email-sent',
|
||||
@ -76,6 +95,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/AccountConfirmationResendView.vue'
|
||||
),
|
||||
props: { action: 'email-sent' },
|
||||
meta: {
|
||||
title: 'buttons.ACCOUNT-CONFIRMATION-RESEND',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/password-reset/sent',
|
||||
@ -85,6 +107,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/PasswordResetView.vue'
|
||||
),
|
||||
props: { action: 'request-sent' },
|
||||
meta: {
|
||||
title: 'user.PASSWORD_RESET',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/password-reset/request',
|
||||
@ -94,6 +119,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/PasswordResetView.vue'
|
||||
),
|
||||
props: { action: 'reset-request' },
|
||||
meta: {
|
||||
title: 'user.PASSWORD_RESET',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/password-reset/password-updated',
|
||||
@ -103,6 +131,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/PasswordResetView.vue'
|
||||
),
|
||||
props: { action: 'password-updated' },
|
||||
meta: {
|
||||
title: 'user.PASSWORD_RESET',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/password-reset',
|
||||
@ -112,6 +143,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/PasswordResetView.vue'
|
||||
),
|
||||
props: { action: 'reset' },
|
||||
meta: {
|
||||
title: 'user.PASSWORD_RESET',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/email-update',
|
||||
@ -120,6 +154,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
import(
|
||||
/* webpackChunkName: 'profile' */ '@/views/user/EmailUpdateView.vue'
|
||||
),
|
||||
meta: {
|
||||
title: 'user.EMAIL_UPDATE',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
@ -139,17 +176,26 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '',
|
||||
name: 'UserInfos',
|
||||
component: UserInfos,
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.PROFILE',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'preferences',
|
||||
name: 'UserPreferences',
|
||||
component: UserPreferences,
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.PREFERENCES',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'sports',
|
||||
name: 'UserSportPreferences',
|
||||
component: UserSportPreferences,
|
||||
props: { isEdition: false },
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.SPORTS',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'apps',
|
||||
@ -160,27 +206,42 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '',
|
||||
name: 'UserAppsList',
|
||||
component: UserAppsList,
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.APPS',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'UserApp',
|
||||
component: UserApp,
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.APPS',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':id/created',
|
||||
name: 'CreatedUserApp',
|
||||
component: UserApp,
|
||||
props: { afterCreation: true },
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.APPS',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'new',
|
||||
name: 'AddUserApp',
|
||||
component: AddUserApp,
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.APPS',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'authorize',
|
||||
name: 'AuthorizeUserApp',
|
||||
component: AuthorizeUserApp,
|
||||
meta: {
|
||||
title: 'user.PROFILE.TABS.APPS',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -198,32 +259,50 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '',
|
||||
name: 'UserInfosEdition',
|
||||
component: UserInfosEdition,
|
||||
meta: {
|
||||
title: 'user.PROFILE.EDIT',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
name: 'UserAccountEdition',
|
||||
component: UserAccountEdition,
|
||||
meta: {
|
||||
title: 'user.PROFILE.ACCOUNT_EDITION',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'picture',
|
||||
name: 'UserPictureEdition',
|
||||
component: UserPictureEdition,
|
||||
meta: {
|
||||
title: 'user.PROFILE.PICTURE_EDITION',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'preferences',
|
||||
name: 'UserPreferencesEdition',
|
||||
component: UserPreferencesEdition,
|
||||
meta: {
|
||||
title: 'user.PROFILE.EDIT_PREFERENCES',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'sports',
|
||||
name: 'UserSportPreferencesEdition',
|
||||
component: UserSportPreferences,
|
||||
props: { isEdition: true },
|
||||
meta: {
|
||||
title: 'user.PROFILE.EDIT_SPORTS_PREFERENCES',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
name: 'UserPrivacyPolicy',
|
||||
component: UserPrivacyPolicyValidation,
|
||||
meta: {
|
||||
title: 'user.PROFILE.PRIVACY-POLICY_EDITION',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -234,12 +313,18 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'Statistics',
|
||||
component: () =>
|
||||
import(/* webpackChunkName: 'statistics' */ '@/views/StatisticsView.vue'),
|
||||
meta: {
|
||||
title: 'statistics.STATISTICS',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/:username',
|
||||
name: 'User',
|
||||
component: () =>
|
||||
import(/* webpackChunkName: 'profile' */ '@/views/user/UserView.vue'),
|
||||
meta: {
|
||||
title: 'administration.USER',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/workouts',
|
||||
@ -248,6 +333,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
import(
|
||||
/* webpackChunkName: 'workouts' */ '@/views/workouts/WorkoutsView.vue'
|
||||
),
|
||||
meta: {
|
||||
title: 'workouts.WORKOUT',
|
||||
count: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/workouts/:workoutId',
|
||||
@ -255,6 +344,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () =>
|
||||
import(/* webpackChunkName: 'workouts' */ '@/views/workouts/Workout.vue'),
|
||||
props: { displaySegment: false },
|
||||
meta: {
|
||||
title: 'workouts.WORKOUT',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/workouts/:workoutId/edit',
|
||||
@ -263,6 +355,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
import(
|
||||
/* webpackChunkName: 'workouts' */ '@/views/workouts/EditWorkout.vue'
|
||||
),
|
||||
meta: {
|
||||
title: 'workouts.EDIT_WORKOUT',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/workouts/:workoutId/segment/:segmentId',
|
||||
@ -270,6 +365,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () =>
|
||||
import(/* webpackChunkName: 'workouts' */ '@/views/workouts/Workout.vue'),
|
||||
props: { displaySegment: true },
|
||||
meta: {
|
||||
title: 'workouts.SEGMENT',
|
||||
count: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/workouts/add',
|
||||
@ -278,6 +377,9 @@ const routes: Array<RouteRecordRaw> = [
|
||||
import(
|
||||
/* webpackChunkName: 'workouts' */ '@/views/workouts/AddWorkout.vue'
|
||||
),
|
||||
meta: {
|
||||
title: 'workouts.ADD_WORKOUT',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
@ -289,22 +391,34 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '',
|
||||
name: 'AdministrationMenu',
|
||||
component: AdminMenu,
|
||||
meta: {
|
||||
title: 'admin.ADMINISTRATION',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'application',
|
||||
name: 'ApplicationAdministration',
|
||||
component: AdminApplication,
|
||||
meta: {
|
||||
title: 'admin.APP_CONFIG.TITLE',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'application/edit',
|
||||
name: 'ApplicationAdministrationEdition',
|
||||
component: AdminApplication,
|
||||
props: { edition: true },
|
||||
meta: {
|
||||
title: 'admin.APPLICATION',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'sports',
|
||||
name: 'SportsAdministration',
|
||||
component: AdminSports,
|
||||
meta: {
|
||||
title: 'admin.SPORTS.TITLE',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'users/:username',
|
||||
@ -312,11 +426,18 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () =>
|
||||
import(/* webpackChunkName: 'profile' */ '@/views/user/UserView.vue'),
|
||||
props: { fromAdmin: true },
|
||||
meta: {
|
||||
title: 'admin.USER',
|
||||
count: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
name: 'UsersAdministration',
|
||||
component: AdminUsers,
|
||||
meta: {
|
||||
title: 'admin.USERS.TITLE',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -324,16 +445,25 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: AboutView,
|
||||
meta: {
|
||||
title: 'common.ABOUT',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/privacy-policy',
|
||||
name: 'PrivacyPolicy',
|
||||
component: PrivacyPolicyView,
|
||||
meta: {
|
||||
title: 'privacy_policy.TITLE',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: NotFoundView,
|
||||
meta: {
|
||||
title: 'error.NOT_FOUND.PAGE',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@ -357,6 +487,17 @@ const pathsWithoutAuthentication = [
|
||||
const pathsWithoutChecks = ['/email-update', '/about', '/privacy-policy']
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if ('title' in to.meta) {
|
||||
const title = typeof to.meta.title === 'string' ? to.meta.title : ''
|
||||
const translatedTitle = title
|
||||
? typeof to.meta.count === 'number'
|
||||
? t(title, +to.meta.count)
|
||||
: t(title)
|
||||
: ''
|
||||
window.document.title = `FitTrackee${
|
||||
title ? ` - ${capitalize(translatedTitle)}` : ''
|
||||
}`
|
||||
}
|
||||
store
|
||||
.dispatch(AUTH_USER_STORE.ACTIONS.CHECK_AUTH_USER)
|
||||
.then(() => {
|
||||
|
@ -79,6 +79,30 @@ button {
|
||||
color: var(--app-color);
|
||||
padding: 6px 14px;
|
||||
|
||||
&.transparent {
|
||||
font-family: 'PT Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 1em;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover, &:disabled {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--app-color);
|
||||
}
|
||||
|
||||
&:enabled:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:disabled, &.confirm:disabled {
|
||||
border-color: transparent;
|
||||
color: var(--disabled-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--app-color);
|
||||
color: var(--button-hover-color);
|
||||
|
@ -1,7 +1,7 @@
|
||||
:root {
|
||||
--app-background-color: #FFFFFF;
|
||||
--app-color: #2c3e50;
|
||||
--app-color-light: #808b96;
|
||||
--app-color-light: #6f7070;
|
||||
--app-a-color: #40578a;
|
||||
--app-shadow-color: lightgrey;
|
||||
--app-loading-color: #f3f3f3;
|
||||
@ -23,8 +23,8 @@
|
||||
--input-error-color: #dc3545;
|
||||
--dropdown-hover-color: #eff0f5;
|
||||
|
||||
--custom-checkbox-border-color: #9da3af;
|
||||
--custom-checkbox-checked-bg-color: #9da3af;
|
||||
--custom-checkbox-border-color: #6d797a;
|
||||
--custom-checkbox-checked-bg-color: #6d797a;
|
||||
--custom-checkbox-checked-color: #FFFFFF;
|
||||
|
||||
--calendar-border-color: #c4c7cf;
|
||||
@ -42,7 +42,7 @@
|
||||
|
||||
--footer-background-color: #FFFFFF;
|
||||
--footer-border-color: #ebeef3;
|
||||
--footer-color: #8b8c8c;
|
||||
--footer-color: #6f7070;
|
||||
|
||||
--alert-background-color: #d6dde3;
|
||||
--alert-color: #3f3f3f;
|
||||
@ -54,7 +54,7 @@
|
||||
--success-color: #306430;
|
||||
|
||||
--disabled-background-color: #e0e0e0;
|
||||
--disabled-color: #a3a3a3;
|
||||
--disabled-color: #727272;
|
||||
--disabled-sport-color: #616161;
|
||||
|
||||
--scroll-button-bg-color: rgba(255, 255, 255, .7);
|
||||
@ -63,7 +63,7 @@
|
||||
--workout-img-color: invert(22%) sepia(25%) saturate(646%) hue-rotate(169deg)
|
||||
brightness(97%) contrast(96%);
|
||||
--workout-no-map-bg-color: #eaeaea;
|
||||
--workout-no-map-color: #666666;
|
||||
--workout-no-map-color: #585959;
|
||||
|
||||
--cell-heading-bg-color: #eeeeee;
|
||||
--cell-heading-color: #696969;
|
||||
@ -76,6 +76,6 @@
|
||||
--password-color-good: #acc578;
|
||||
--password-color-strong: #57c255;
|
||||
|
||||
--scroll-thumb-color: #b9bcbd;
|
||||
--scroll-thumb-color: #949697;
|
||||
|
||||
}
|
@ -5,9 +5,10 @@
|
||||
<template #title>{{ $t('statistics.STATISTICS') }}</template>
|
||||
<template #content>
|
||||
<Statistics
|
||||
:class="{ 'stats-disabled': authUser.nb_workouts === 0 }"
|
||||
:class="{ 'stats-disabled': isDisabled }"
|
||||
:user="authUser"
|
||||
:sports="sports"
|
||||
:isDisabled="isDisabled"
|
||||
/>
|
||||
</template>
|
||||
</Card>
|
||||
@ -36,6 +37,9 @@
|
||||
authUser.value.sports_list.includes(sport.id)
|
||||
)
|
||||
)
|
||||
const isDisabled: ComputedRef<boolean> = computed(
|
||||
() => authUser.value.nb_workouts === 0
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
Loading…
Reference in New Issue
Block a user