Client - init "About" page and admin contact in footer

+  add missing translations
This commit is contained in:
Sam 2022-03-26 09:46:15 +01:00
parent 49100c27e7
commit 614c888ec4
17 changed files with 212 additions and 27 deletions

View File

@ -19,7 +19,11 @@
<i class="fa fa-chevron-up" aria-hidden="true"></i> <i class="fa fa-chevron-up" aria-hidden="true"></i>
</div> </div>
</div> </div>
<Footer v-if="appConfig" :version="appConfig ? appConfig.version : ''" /> <Footer
v-if="appConfig"
:version="appConfig ? appConfig.version : ''"
:adminContact="appConfig.admin_contact"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -0,0 +1,72 @@
<template>
<div class="about-text">
<div>
<p class="error-message" v-html="$t('about.FITTRACKEE_DESCRIPTION')" />
<p>
<i class="fa fa-book fa-padding" aria-hidden="true"></i>
<a
href="https://samr1.github.io/FitTrackee/"
target="_blank"
rel="noopener noreferrer"
>
{{ capitalize($t('common.DOCUMENTATION')) }}
</a>
</p>
<p>
<i class="fa fa-github fa-padding" aria-hidden="true"></i>
<a
href="https://github.com/SamR1/FitTrackee"
target="_blank"
rel="noopener noreferrer"
>
{{ $t('about.SOURCE_CODE') }}
</a>
</p>
<p>
<i class="fa fa-balance-scale fa-padding" aria-hidden="true"></i>
<i18n-t keypath="about.FITTRACKEE_LICENSE">
<a
href="https://choosealicense.com/licenses/agpl-3.0/"
target="_blank"
rel="noopener noreferrer"
>
AGPLv3
</a>
</i18n-t>
</p>
<div v-if="appConfig.admin_contact">
<i class="fa fa-envelope-o fa-padding" aria-hidden="true"></i>
<a :href="`mailto:${appConfig.admin_contact}`">
{{ $t('about.CONTACT_ADMIN') }}
</a>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ComputedRef, computed, capitalize } from 'vue'
import { ROOT_STORE } from '@/store/constants'
import { TAppConfig } from '@/types/application'
import { useStore } from '@/use/useStore'
const store = useStore()
const appConfig: ComputedRef<TAppConfig> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
)
</script>
<style lang="scss" scoped>
@import '~@/scss/base.scss';
.about-text {
margin-top: 200px;
@media screen and (max-width: $small-limit) {
margin-top: 0;
}
.fa-padding {
padding-right: $default-padding;
}
}
</style>

View File

@ -4,6 +4,23 @@
<template #title>{{ $t('admin.APP_CONFIG.TITLE') }}</template> <template #title>{{ $t('admin.APP_CONFIG.TITLE') }}</template>
<template #content> <template #content>
<form class="admin-form" @submit.prevent="onSubmit"> <form class="admin-form" @submit.prevent="onSubmit">
<label for="admin_contact">
{{ $t('admin.APP_CONFIG.ADMIN_CONTACT') }}:
<input
class="no-contact"
v-if="!edition && !appData.admin_contact"
:value="$t('admin.APP_CONFIG.NO_CONTACT_EMAIL')"
disabled
/>
<input
v-else
id="admin_contact"
name="admin_contact"
type="email"
v-model="appData.admin_contact"
:disabled="!edition"
/>
</label>
<label for="max_users"> <label for="max_users">
{{ $t('admin.APP_CONFIG.MAX_USERS_LABEL') }}: {{ $t('admin.APP_CONFIG.MAX_USERS_LABEL') }}:
<input <input
@ -89,6 +106,7 @@
reactive, reactive,
withDefaults, withDefaults,
onBeforeMount, onBeforeMount,
toRefs,
} from 'vue' } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -104,11 +122,13 @@
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
edition: false, edition: false,
}) })
const { edition } = toRefs(props)
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()
const appData: TAppConfigForm = reactive({ const appData: TAppConfigForm = reactive({
admin_contact: '',
max_users: 0, max_users: 0,
max_single_file_size: 0, max_single_file_size: 0,
max_zip_file_size: 0, max_zip_file_size: 0,
@ -160,4 +180,7 @@
margin-right: $default-margin; margin-right: $default-margin;
} }
} }
.no-contact {
font-style: italic;
}
</style> </style>

View File

@ -7,21 +7,13 @@
</div> </div>
<div class="footer-item bullet"></div> <div class="footer-item bullet"></div>
<div class="footer-item"> <div class="footer-item">
<a <router-link to="/about">
href="https://github.com/SamR1/FitTrackee" {{ $t('common.ABOUT') }}
target="_blank" </router-link>
rel="noopener noreferrer" </div>
> <div class="footer-item bullet" v-if="adminContact"></div>
source code <div class="footer-item" v-if="adminContact">
</a> <a :href="`mailto:${adminContact}`">{{ $t('common.CONTACT') }}</a>
under
<a
href="https://choosealicense.com/licenses/agpl-3.0/"
target="_blank"
rel="noopener noreferrer"
>
AGPLv3 </a
>license
</div> </div>
<div class="footer-item bullet"></div> <div class="footer-item bullet"></div>
<div class="footer-item"> <div class="footer-item">
@ -30,7 +22,7 @@
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
documentation {{ $t('common.DOCUMENTATION') }}
</a> </a>
</div> </div>
</div> </div>
@ -42,10 +34,11 @@
interface Props { interface Props {
version: string version: string
adminContact?: string
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const { version } = toRefs(props) const { adminContact, version } = toRefs(props)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -0,0 +1,6 @@
{
"CONTACT_ADMIN": "Contact the administrator",
"FITTRACKEE_DESCRIPTION": "<strong>FitTrackee</strong> is a self-hosted outdoor activity tracker.",
"FITTRACKEE_LICENSE": "under {0} license ",
"SOURCE_CODE": "Source code"
}

View File

@ -7,9 +7,11 @@
"ADMINISTRATION": "Administration", "ADMINISTRATION": "Administration",
"APPLICATION": "Application", "APPLICATION": "Application",
"APP_CONFIG": { "APP_CONFIG": {
"ADMIN_CONTACT": "Administrator email for contact",
"MAX_USERS_LABEL": "Max. number of active users", "MAX_USERS_LABEL": "Max. number of active users",
"MAX_USERS_HELP": "If 0, no limitation on registration.", "MAX_USERS_HELP": "If 0, no limitation on registration.",
"MAX_FILES_IN_ZIP_LABEL": "Max. files of zip archive", "MAX_FILES_IN_ZIP_LABEL": "Max. files of zip archive",
"NO_CONTACT_EMAIL": "no contact email",
"SINGLE_UPLOAD_MAX_SIZE_LABEL": "Max. size of uploaded files (in Mb)", "SINGLE_UPLOAD_MAX_SIZE_LABEL": "Max. size of uploaded files (in Mb)",
"TITLE": "Application configuration", "TITLE": "Application configuration",
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Max. size of zip archive (in Mb)" "ZIP_UPLOAD_MAX_SIZE_LABEL": "Max. size of zip archive (in Mb)"

View File

@ -23,6 +23,7 @@
"signature expired, please log in again": "Signature expired. Please log in again.", "signature expired, please log in again": "Signature expired. Please log in again.",
"successfully registered": "Successfully registered.", "successfully registered": "Successfully registered.",
"user does not exist": "User does not exist.", "user does not exist": "User does not exist.",
"valid email must be provided for admin contact": "A valid email must be provided for admininstrator contact",
"you can not delete your account, no other user has admin rights": "You can not delete your account, no other user has admin rights.", "you can not delete your account, no other user has admin rights": "You can not delete your account, no other user has admin rights.",
"you do not have permissions": "You do not have permissions." "you do not have permissions": "You do not have permissions."
}, },

View File

@ -1,6 +1,9 @@
{ {
"ABOUT": "about",
"CONFIRMATION": "Confirmation", "CONFIRMATION": "Confirmation",
"CONTACT": "contact",
"DAY": "day | days", "DAY": "day | days",
"DOCUMENTATION": "documentation",
"HOME": "Home", "HOME": "Home",
"HERE": "here", "HERE": "here",
"SELECTS": { "SELECTS": {

View File

@ -1,3 +1,4 @@
import AboutTranslations from './about.json'
import AdministrationTranslations from './administration.json' import AdministrationTranslations from './administration.json'
import ApiTranslations from './api.json' import ApiTranslations from './api.json'
import ButtonsTranslations from './buttons.json' import ButtonsTranslations from './buttons.json'
@ -10,6 +11,7 @@ import UserTranslations from './user.json'
import WorkoutsTranslations from './workouts.json' import WorkoutsTranslations from './workouts.json'
export default { export default {
about: AboutTranslations,
admin: AdministrationTranslations, admin: AdministrationTranslations,
api: ApiTranslations, api: ApiTranslations,
buttons: ButtonsTranslations, buttons: ButtonsTranslations,

View File

@ -0,0 +1,6 @@
{
"CONTACT_ADMIN": "Contacter l'administrateur",
"FITTRACKEE_DESCRIPTION": "<strong>FitTrackee</strong> est un <em>tracker</em> d'activités sportives (en extérieur).",
"FITTRACKEE_LICENSE": "sous license {0} (en)",
"SOURCE_CODE": "Code source (en)"
}

View File

@ -7,9 +7,11 @@
"ADMINISTRATION": "Administration", "ADMINISTRATION": "Administration",
"APPLICATION": "Application", "APPLICATION": "Application",
"APP_CONFIG": { "APP_CONFIG": {
"ADMIN_CONTACT": "Email de l'administrateur pour contact ",
"MAX_USERS_LABEL": "Nombre maximum d'utilisateurs actifs ", "MAX_USERS_LABEL": "Nombre maximum d'utilisateurs actifs ",
"MAX_USERS_HELP": "Si égal à 0, pas limite d'inscription", "MAX_USERS_HELP": "Si égal à 0, pas limite d'inscription",
"MAX_FILES_IN_ZIP_LABEL": "Taille max. des archives zip (en Mo) ", "MAX_FILES_IN_ZIP_LABEL": "Taille max. des archives zip (en Mo) ",
"NO_CONTACT_EMAIL": "non renseigné",
"SINGLE_UPLOAD_MAX_SIZE_LABEL": "Taille max. des fichiers (en Mo) ", "SINGLE_UPLOAD_MAX_SIZE_LABEL": "Taille max. des fichiers (en Mo) ",
"TITLE": "Configuration de l'application", "TITLE": "Configuration de l'application",
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip " "ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip "

View File

@ -23,6 +23,7 @@
"sorry, that username is already taken": "Désolé, ce nom d'utilisateur est déjà utilisé.", "sorry, that username is already taken": "Désolé, ce nom d'utilisateur est déjà utilisé.",
"successfully registered": "Inscription validée.", "successfully registered": "Inscription validée.",
"user does not exist": "L'utilisateur n'existe pas", "user does not exist": "L'utilisateur n'existe pas",
"valid email must be provided for admin contact": "Une adresse email doit être fournie pour le contact de l'administrateur.",
"you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.", "you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",
"you do not have permissions": "Vous n'avez pas les permissions nécessaires." "you do not have permissions": "Vous n'avez pas les permissions nécessaires."
}, },

View File

@ -1,6 +1,9 @@
{ {
"ABOUT": "à propos",
"CONFIRMATION": "Confirmation", "CONFIRMATION": "Confirmation",
"CONTACT": "contact",
"DAY": "jour | jours", "DAY": "jour | jours",
"DOCUMENTATION": "documentation (en)",
"HOME": "Accueil", "HOME": "Accueil",
"HERE": "ici", "HERE": "ici",
"SELECTS": { "SELECTS": {

View File

@ -1,3 +1,4 @@
import AboutTranslations from './about.json'
import AdministrationTranslations from './administration.json' import AdministrationTranslations from './administration.json'
import ApiTranslations from './api.json' import ApiTranslations from './api.json'
import ButtonsTranslations from './buttons.json' import ButtonsTranslations from './buttons.json'
@ -10,6 +11,7 @@ import UserTranslations from './user.json'
import WorkoutsTranslations from './workouts.json' import WorkoutsTranslations from './workouts.json'
export default { export default {
about: AboutTranslations,
admin: AdministrationTranslations, admin: AdministrationTranslations,
api: ApiTranslations, api: ApiTranslations,
buttons: ButtonsTranslations, buttons: ButtonsTranslations,

View File

@ -15,6 +15,7 @@ import UserPreferencesEdition from '@/components/User/ProfileEdition/UserPrefere
import UserSportPreferences from '@/components/User/UserSportPreferences.vue' import UserSportPreferences from '@/components/User/UserSportPreferences.vue'
import store from '@/store' import store from '@/store'
import { AUTH_USER_STORE } from '@/store/constants' import { AUTH_USER_STORE } from '@/store/constants'
import AboutView from '@/views/AboutView.vue'
import Dashboard from '@/views/Dashboard.vue' import Dashboard from '@/views/Dashboard.vue'
import NotFoundView from '@/views/NotFoundView.vue' import NotFoundView from '@/views/NotFoundView.vue'
import LoginOrRegister from '@/views/user/LoginOrRegister.vue' import LoginOrRegister from '@/views/user/LoginOrRegister.vue'
@ -274,6 +275,11 @@ const routes: Array<RouteRecordRaw> = [
}, },
], ],
}, },
{
path: '/about',
name: 'About',
component: AboutView,
},
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
name: 'not-found', name: 'not-found',
@ -298,7 +304,7 @@ const pathsWithoutAuthentication = [
'/account-confirmation/email-sent', '/account-confirmation/email-sent',
] ]
const pathsWithoutChecks = ['/email-update'] const pathsWithoutChecks = ['/email-update', '/about']
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
store store

View File

@ -7,7 +7,7 @@ export interface IAppStatistics {
export type TAppConfig = { export type TAppConfig = {
[key: string]: number | boolean | string [key: string]: number | boolean | string
federation_enabled: boolean admin_contact: string
gpx_limit_import: number gpx_limit_import: number
is_registration_enabled: boolean is_registration_enabled: boolean
map_attribution: string map_attribution: string
@ -23,7 +23,8 @@ export interface IApplication {
} }
export type TAppConfigForm = { export type TAppConfigForm = {
[key: string]: number [key: string]: number | string
admin_contact: string
gpx_limit_import: number gpx_limit_import: number
max_single_file_size: number max_single_file_size: number
max_users: number max_users: number

View File

@ -0,0 +1,58 @@
<template>
<div id="about" class="view">
<div class="container">
<div class="container-sub">
<BikePic />
</div>
<div class="container-sub about-details">
<About />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import About from '@/components/About.vue'
import BikePic from '@/components/BikePic.vue'
</script>
<style lang="scss" scoped>
@import '~@/scss/vars.scss';
#about {
display: flex;
height: 100%;
.container {
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin-bottom: $default-margin * 2;
width: 100%;
.container-sub {
min-width: 50%;
height: 100%;
}
.about-details {
width: 100%;
}
}
@media screen and (max-width: $medium-limit) {
height: auto;
.container {
.container-sub {
align-items: center;
.bike-img {
max-width: 60%;
}
}
}
}
@media screen and (max-width: $small-limit) {
.container {
flex-direction: column;
}
}
}
</style>