This commit is contained in:
Matthieu Bessat 2020-07-18 00:10:58 +02:00
parent 4a94799602
commit de2e56baae
13 changed files with 314 additions and 171 deletions

View file

@ -12,7 +12,7 @@
"register-service-worker": "^1.7.1", "register-service-worker": "^1.7.1",
"tiptap-vuetify": "^2.23.0", "tiptap-vuetify": "^2.23.0",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-apitator": "^0.0.16", "vue-apitator": "0.0.17",
"vue-class-component": "^7.2.3", "vue-class-component": "^7.2.3",
"vue-croppa": "^1.3.8", "vue-croppa": "^1.3.8",
"vue-input-facade": "^1.3.1", "vue-input-facade": "^1.3.1",

View file

@ -20,6 +20,9 @@ export default Vue.extend({
created () { created () {
console.log('Using ' + process.env.VUE_APP_BASE_URL) console.log('Using ' + process.env.VUE_APP_BASE_URL)
if (window.localStorage.getItem('associations_condorcet_debug') === 'YES') {
this.$store.commit('SET_DEBUG', true)
}
}, },
data: () => ({}) data: () => ({})

View file

@ -6,7 +6,7 @@
:vertical="$vuetify.breakpoint.mobile" :vertical="$vuetify.breakpoint.mobile"
:multi-line="$vuetify.breakpoint.mobile" :multi-line="$vuetify.breakpoint.mobile"
v-model="$store.state.alert.enabled" v-model="$store.state.alert.enabled"
:timeout="500000" :timeout="2500"
> >
{{ $store.state.alert.text }} {{ $store.state.alert.text }}

View file

@ -58,7 +58,7 @@
<v-tab @click="navigate('DelegateGallery')"> <v-tab @click="navigate('DelegateGallery')">
Images/vidéos Images/vidéos
</v-tab> </v-tab>
<v-tab @click="navigate('DelegatePresentation')"> <v-tab @click="navigate('DelegateDescription')">
Description Description
</v-tab> </v-tab>
<v-tab @click="navigate('DelegateSchedule')"> <v-tab @click="navigate('DelegateSchedule')">
@ -254,7 +254,7 @@ export default {
selectRoute (route) { selectRoute (route) {
const path = route.path.split('/') const path = route.path.split('/')
const name = path[path.length - 1] const name = path[path.length - 1]
const routes = ['', 'gallery', 'presentation', 'schedule', 'pricing', 'contact'] const routes = ['', 'gallery', 'description', 'schedule', 'pricing', 'contact']
this.tab = routes.indexOf(name) this.tab = routes.indexOf(name)
}, },
init () { init () {
@ -374,15 +374,22 @@ export default {
} }
}, },
goToPage () { goToPage () {
window.open(this.publicUrl, '_blank').focus() window.open(this.$store.state.delegate.publicUrl, '_blank').focus()
}, },
openPublishModal () { openPublishModal () {
// compute if the user can ask approval // compute if the user can ask approval
this.publishModal = true this.$store.commit('VALIDATE_MAIN', isValid => {
const data = this.$store.state.data if (isValid) {
this.canPublish = this.publishModal = true
data.name.length > 0 && } else {
data.descriptionShort.length > 10 this.$store.commit('ADD_ALERT', {
color: 'error',
text: 'Vous devez remplir tout les champs requis afin de publier'
})
this.publishModal = false
}
this.canPublish = isValid
})
}, },
askApproval () { askApproval () {
this.askingApprovalLoading = true this.askingApprovalLoading = true

View file

@ -46,14 +46,14 @@ const routes: Array<RouteConfig> = [
component: () => import(/* webpackChunkName: "delegateMain" */ '../views/Delegate/Main.vue') component: () => import(/* webpackChunkName: "delegateMain" */ '../views/Delegate/Main.vue')
}, },
{ {
path: 'contact', path: 'gallery',
name: 'DelegateContact', name: 'DelegateGallery',
component: () => import(/* webpackChunkName: "delegateContact" */ '../views/Delegate/Contact.vue') component: () => import(/* webpackChunkName: "delegateGallery" */ '../views/Delegate/Gallery.vue')
}, },
{ {
path: 'pricing', path: 'description',
name: 'DelegatePricing', name: 'DelegateDescription',
component: () => import(/* webpackChunkName: "delegatePricing" */ '../views/Delegate/Pricing.vue') component: () => import(/* webpackChunkName: "delegateDescription" */ '../views/Delegate/Description.vue')
}, },
{ {
path: 'schedule', path: 'schedule',
@ -61,16 +61,21 @@ const routes: Array<RouteConfig> = [
component: () => import(/* webpackChunkName: "delegateSchedule" */ '../views/Delegate/Schedule.vue') component: () => import(/* webpackChunkName: "delegateSchedule" */ '../views/Delegate/Schedule.vue')
}, },
{ {
path: 'gallery', path: 'pricing',
name: 'DelegateGallery', name: 'DelegatePricing',
component: () => import(/* webpackChunkName: "delegateGallery" */ '../views/Delegate/Gallery.vue') component: () => import(/* webpackChunkName: "delegatePricing" */ '../views/Delegate/Pricing.vue')
}, },
{ {
path: 'presentation', path: 'contact',
name: 'DelegatePresentation', name: 'DelegateContact',
component: () => import(/* webpackChunkName: "delegatePresentation" */ '../views/Delegate/Presentation.vue') component: () => import(/* webpackChunkName: "delegateContact" */ '../views/Delegate/Contact.vue')
} }
] ]
},
{
path: '*',
name: 'NotFound',
component: () => import(/* webpackChunkName: "notFound" */ '../views/NotFound.vue')
} }
] ]

View file

@ -38,11 +38,18 @@ export default new Vuex.Store({
admiName: '', admiName: '',
email: '', email: '',
publicUrl: '' publicUrl: ''
} },
debug: false,
validateMain: false,
validateMainCallback: () => false
}, },
mutations: { mutations: {
SET_DEBUG (state, payload) {
state.debug = payload
},
SET_TITLE (state, payload) { SET_TITLE (state, payload) {
state.title = payload state.title = payload
window.document.title = state.title + ' | Panel forum des associations'
}, },
ADD_ALERT (state, payload) { ADD_ALERT (state, payload) {
state.alert = { state.alert = {
@ -64,6 +71,13 @@ export default new Vuex.Store({
}, },
SET_DELEGATE (state, payload) { SET_DELEGATE (state, payload) {
state.delegate = { ...state.delegate, ...payload } state.delegate = { ...state.delegate, ...payload }
},
VALIDATE_MAIN (state, callback) {
state.validateMain = true
state.validateMainCallback = () => {
state.validateMain = false
return callback()
}
} }
}, },
actions: { actions: {

View file

@ -1,26 +1,13 @@
<template> <template>
<div> <div>
<div class="mb-3 pb-1"> <div class="mb-1 pb-1">
<v-alert <v-alert
border="left" dense
colored-border
type="info" type="info"
elevation="1"
> >
Ici vous pouvez préciser quelques manières de contacter votre association, aucun des champs indiqués n'est obligatoire. Ici vous pouvez préciser quelques manières de contacter votre association, aucun des champs indiqués n'est obligatoire.
</v-alert> </v-alert>
</div> </div>
<v-text-field
prepend-icon="person"
label="Personne responsable"
outlined
v-model="$store.state.data.contacts.person" />
<v-text-field
prepend-icon="alternate_email"
label="Email"
:rules="rules.email"
outlined
v-model="$store.state.data.contacts.email" />
<v-text-field <v-text-field
prepend-icon="call" prepend-icon="call"
label="Numéro de téléphone" label="Numéro de téléphone"
@ -65,9 +52,6 @@ export default {
website: [ website: [
v => v === '' || /.+@.+\..+/.test(v) || "L'email doit être valide" v => v === '' || /.+@.+\..+/.test(v) || "L'email doit être valide"
], ],
email: [
v => v === '' || /.+@.+\..+/.test(v) || "L'email doit être valide"
],
address: [ address: [
v => v.length < 200 || 'Maximum 200 caractères' v => v.length < 200 || 'Maximum 200 caractères'
], ],

View file

@ -1,36 +1,37 @@
<template> <template>
<div> <div>
<!-- COVER START --> <!-- COVER START -->
<div <div v-if="false" class="mb-3">
class="cover-common cover-container" <div
:style="coverStyle"> class="cover-common cover-container"
</div> :style="coverStyle">
<div class="cover-common cover-upper"> </div>
<div> <div class="cover-common cover-upper">
<v-btn <div>
@click="$refs.avatarEditor.toggle()" <v-btn
color="white" @click="$refs.avatarEditor.toggle()"
>Changer la couverture</v-btn> color="white"
<v-tooltip bottom> >Changer la couverture</v-btn>
<template v-slot:activator="{ on, attrs }"> <v-tooltip bottom>
<v-btn <template v-slot:activator="{ on, attrs }">
v-if="$store.state.data.cover !== null" <v-btn
class="ml-3" v-if="$store.state.data.cover !== null"
@click="clearCover()" class="ml-3"
color="white" @click="clearCover()"
v-bind="attrs" color="white"
v-on="on" v-bind="attrs"
icon v-on="on"
outlined icon
> outlined
<v-icon>clear</v-icon> >
</v-btn> <v-icon>clear</v-icon>
</template> </v-btn>
<span>Supprimer la couverture</span> </template>
</v-tooltip> <span>Supprimer la couverture</span>
</v-tooltip>
</div>
</div> </div>
</div> </div>
<AvatarEditor <AvatarEditor
ref="avatarEditor" ref="avatarEditor"
:loading="coverLoading" :loading="coverLoading"
@ -41,10 +42,10 @@
@submitted="handleAvatarEditorSubmitted" @submitted="handleAvatarEditorSubmitted"
/> />
<!-- COVER END --> <!-- COVER END -->
<div class="d-flex justify-end mt-3 mb-3"> <div class="d-flex justify-end mb-3">
<v-btn color="primary" outlined @click="addMediaModal = true"> <v-btn color="primary" outlined @click="addMediaModal = true">
<v-icon left>add</v-icon> <v-icon left>add</v-icon>
Ajouter une image Ajouter un média
</v-btn> </v-btn>
</div> </div>
<div <div
@ -56,10 +57,10 @@
outlined outlined
type="info" type="info"
> >
Aucun medias n'ont été ajoutés pour l'instant Aucun médias n'ont été ajoutés pour l'instant
</v-alert> </v-alert>
</div> </div>
<v-row v-else> <v-row v-else class="justify-center">
<v-col <v-col
v-for="(media, index) in $store.state.data.gallery" v-for="(media, index) in $store.state.data.gallery"
:key="media._id" :key="media._id"
@ -109,6 +110,22 @@
<span>Décaler vers la droite</span> <span>Décaler vers la droite</span>
</v-tooltip> </v-tooltip>
<v-spacer /> <v-spacer />
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
icon
color="teal"
small
v-bind="attrs"
v-on="on"
@click="shiftRightMedia(media)"
>
<v-icon small>stars</v-icon>
</v-btn>
</template>
<span>Choisir comme couverture</span>
</v-tooltip>
<v-btn icon color="error" small @click="openDeleteMediaModal(media)"> <v-btn icon color="error" small @click="openDeleteMediaModal(media)">
<v-icon small>delete</v-icon> <v-icon small>delete</v-icon>
</v-btn> </v-btn>
@ -124,14 +141,27 @@
<v-card-text> <v-card-text>
<div class="mt-2"> <div class="mt-2">
<v-file-input <v-file-input
prepend-icon="perm_media"
v-model="files" v-model="files"
chips chips
multiple multiple
show-size show-size
counter counter
accept="image/png,image/jpeg,video/mp4"
label="Sélectionnez les images ou vidéos à importer"> label="Sélectionnez les images ou vidéos à importer">
</v-file-input> </v-file-input>
</div> </div>
<div v-if="uploadLoading" class="mt-3">
<v-progress-linear
color="purple"
height="2em"
v-model="uploadProgress"
>
<template v-slot="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
</div>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-btn text color="primary" @click="addMediaModal = false"> <v-btn text color="primary" @click="addMediaModal = false">
@ -172,7 +202,8 @@ export default {
uploadLoading: false, uploadLoading: false,
files: [], files: [],
deleteMediaModal: false, deleteMediaModal: false,
mediaToDelete: {} mediaToDelete: {},
uploadProgress: 50
}), }),
computed: { computed: {
@ -225,7 +256,17 @@ export default {
const formData = new FormData() const formData = new FormData()
results.forEach(r => formData.append(r[0], new Blob([new Uint8Array(r[1])]), r[2])) results.forEach(r => formData.append(r[0], new Blob([new Uint8Array(r[1])]), r[2]))
this.$apitator.post('/delegate/medias', formData, { withAuth: true }).then(res => { this.$apitator.post('/delegate/medias', formData, {
withAuth: true,
onUploadProgress: (progressEvent) => {
console.log(progressEvent)
const totalLength = progressEvent.lengthComputable ? progressEvent.total : progressEvent.target.getResponseHeader('content-length') || progressEvent.target.getResponseHeader('x-decompressed-content-length')
console.log('onUploadProgress', totalLength)
if (totalLength !== null) {
this.uploadProgress = Math.round((progressEvent.loaded * 100) / totalLength)
}
}
}).then(res => {
this.$store.commit('SET_DATA', { gallery: res.data.data.gallery }) this.$store.commit('SET_DATA', { gallery: res.data.data.gallery })
this.$store.commit('ADD_ALERT', { this.$store.commit('ADD_ALERT', {
color: 'success', color: 'success',

View file

@ -1,46 +1,79 @@
<template> <template>
<div> <div>
<v-row class="mb-6 pb-2"> <div class="">
<v-col cols="12" sm="12" md="6" style="align-items: center; justify-content: center; display: flex;"> <v-alert
<v-avatar size="200" style="border: 2px solid #95a5a6"> outlined
<img :src="$store.state.data.thumbnail.location" /> dense
</v-avatar> type="info"
</v-col> >
<v-col cols="12" sm="12" md="6" style="align-items: center; justify-content: center; display: flex;"> Tout les champs indiqués ici sont obligatoire afin de publier votre page
<v-btn @click="$refs.avatarEditor.toggle()" color="primary" outlined> </v-alert>
<v-icon left> </div>
edit <v-form
</v-icon> ref="form"
Changer le logo v-model="$store.state.mainFormIsValid"
</v-btn> lazy-validation>
</v-col> <v-row class="mb-6 pb-2">
</v-row> <v-col cols="12" sm="6" style="align-items: center; justify-content: center; display: flex;">
<v-avatar size="200" style="border: 2px solid #95a5a6">
<img :src="$store.state.data.thumbnail.location" />
</v-avatar>
</v-col>
<v-col cols="12" sm="6" style="align-items: center; justify-content: center; display: flex;">
<v-btn @click="$refs.avatarEditor.toggle()" color="primary" outlined>
<v-icon left>
edit
</v-icon>
Changer le logo
</v-btn>
</v-col>
</v-row>
<v-text-field <v-text-field
label="Nom de l'association" label="Nom de l'association"
prepend-icon="tag" prepend-icon="tag"
outlined outlined
v-model="$store.state.data.name" /> v-model="$store.state.data.name"
:rules="rules.name" />
<v-select <v-select
label="Catégorie de l'association" label="Catégorie(s) de l'association"
outlined outlined
prepend-icon="category" prepend-icon="category"
item-text="name" item-text="name"
item-value="_id" item-value="_id"
v-model="$store.state.data.tag" v-model="$store.state.data.tag"
:items="$store.state.tags"> :items="$store.state.tags">
</v-select> </v-select>
<v-textarea <v-row>
outlined <v-col cols="12" sm="6" class="py-0">
prepend-icon="description" <v-text-field
label="Description ou résumé rapide" prepend-icon="person"
placeholder="Courte description qui apparaitra sur la page d'accueil" label="Personne responsable"
:rules="rules.descriptionShort" outlined
counter v-model="$store.state.data.contacts.person"
v-model="$store.state.data.descriptionShort" /> :rules="rules.person" />
</v-col>
<v-col cols="12" sm="6" class="py-0">
<v-text-field
prepend-icon="alternate_email"
label="Email"
outlined
v-model="$store.state.data.contacts.email"
:rules="rules.email" />
</v-col>
</v-row>
<!-- placeholder="Courte description qui apparaitra sur la page d'accueil" -->
<v-textarea
outlined
prepend-icon="description"
label="Description ou résumé rapide"
:rules="rules.descriptionShort"
counter
v-model="$store.state.data.descriptionShort" />
</v-form>
<AvatarEditor <AvatarEditor
ref="avatarEditor" ref="avatarEditor"
:loading="logoLoading" :loading="logoLoading"
@ -56,10 +89,19 @@ export default {
components: { AvatarEditor }, components: { AvatarEditor },
data: () => ({ data: () => ({
formValid: true,
logoLoading: false, logoLoading: false,
rules: { rules: {
descriptionShort: [v => v.length <= 200 || 'Au maximum 200 caractères'], name: [
logo: [v => !v || v.size < 2000000 || 'La taille du logo doit être inférieur à 2 Mega Octets!'] v => v.length >= 3 || 'Au minimum 3 caractères',
v => v.length <= 50 || 'Au maximum 50 caractères'
],
descriptionShort: [
v => v.length >= 20 || 'Au minimum 20 caractères',
v => v.length <= 200 || 'Au maximum 200 caractères'
],
email: [v => /.+@.+\..+/.test(v) || "L'email est requis et dois être valide"],
person: [v => v.length >= 4 || 'Au minimum 4 caractères']
} }
}), }),
@ -70,6 +112,14 @@ export default {
// }) // })
// }, // },
watch: {
'$store.state.validateMain' (val) {
if (val) {
this.$store.state.validateMainCallback(this.$refs.form.validate())
}
}
},
methods: { methods: {
handleAvatarEditorSubmitted: function (blob) { handleAvatarEditorSubmitted: function (blob) {
const form = new FormData() const form = new FormData()

View file

@ -1,58 +1,54 @@
<template> <template>
<div class="home"> <v-container>
<v-main> <h2 class="text-center">Vous êtes :</h2>
<v-container>
<h2 class="text-center">Vous êtes :</h2>
<v-row class="justify-center"> <v-row class="justify-center">
<v-col <v-col
cols="12" cols="12"
sm="12" sm="12"
md="6" md="6"
lg="4" lg="4"
> >
<v-list> <v-list>
<v-divider /> <v-divider />
<div <div
v-for="nav in navItems" v-for="nav in navItems"
:key="nav.target"> :key="nav.target">
<v-list-item <v-list-item
:to="{ name: nav.target }" :to="{ name: nav.target }"
ripple ripple
class="d-flex justify-center" class="d-flex justify-center"
> >
<v-list-item-avatar> <v-list-item-avatar>
<v-icon color="primary">{{ nav.icon }}</v-icon> <v-icon color="primary">{{ nav.icon }}</v-icon>
</v-list-item-avatar> </v-list-item-avatar>
<v-list-item-content style="flex: initial;"> <v-list-item-content style="flex: initial;">
<v-list-item-title> <v-list-item-title>
{{ nav.name }} {{ nav.name }}
</v-list-item-title> </v-list-item-title>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
<v-divider /> <v-divider />
</div> </div>
<!-- <v-divider /> <!-- <v-divider />
<v-list-item :to="{name: 'OrganizationList'}" ripple> <v-list-item :to="{name: 'OrganizationList'}" ripple>
<v-list-item-avatar> <v-list-item-avatar>
<v-icon color="error">build</v-icon> <v-icon color="error">build</v-icon>
</v-list-item-avatar> </v-list-item-avatar>
<v-list-item-content style="flex: initial;"> <v-list-item-content style="flex: initial;">
<v-list-item-title> <v-list-item-title>
Un administrateur Un administrateur
</v-list-item-title> </v-list-item-title>
</v-list-item-content> </v-list-item-content>
</v-list-item> --> </v-list-item> -->
</v-list> </v-list>
</v-col> </v-col>
<!-- <ul> <!-- <ul>
<li><router-link :to="{name: 'DelegateMain'}">Une association</router-link></li> <li><router-link :to="{name: 'DelegateMain'}">Une association</router-link></li>
<li><router-link :to="{name: 'OrganizationList'}">Administrateur de l'Espace Condorcet Centre Social</router-link></li> <li><router-link :to="{name: 'OrganizationList'}">Administrateur de l'Espace Condorcet Centre Social</router-link></li>
</ul> --> </ul> -->
</v-row> </v-row>
</v-container> </v-container>
</v-main>
</div>
</template> </template>
<script> <script>

43
src/views/NotFound.vue Normal file
View file

@ -0,0 +1,43 @@
<template>
<v-container fluid class="gradient">
<v-layout class="d-flex justify-center mt-3">
<v-card class="text-center pt-10 pb-1 px-5 mt-5" max-width="500px">
<v-card-text class="black--text">
<div style="font-size: 5em">🤷🤷</div>
<div class="text-h4 mb-3 mt-10">Page non trouvée !</div>
<v-divider />
<div class="text-subtitle-1 mt-2">
Avez-vous essayé de l'éteindre et de le rallumer?
<span style="font-size: 1.5em">🔌</span>
</div>
<div class="text-subtitle-1 mt-3 mb-1 text-left">
Liens magiques :
<ul>
<li><a href="https://www.youtube.com/watch?v=2B1ub5g5L0k">Pour les gens friant de video (très émetteur)</a></li>
<li><a href="https://www.youtube.com/watch?v=StfLy3pzno0">Pour les gens qui veulent pas trop consommer de flux vidéo et donc émettre moins de CO2</a></li>
</ul>
</div>
<blockquote class="grey--text text-left">
Si vous voyez des carrés blancs à la place d'emojis alors vous devez mettre à jour votre navigateur ou pire, changer d'OS
</blockquote>
</v-card-text>
</v-card>
</v-layout>
</v-container>
</template>
<script>
export default {
mounted () {
this.$store.commit('SET_TITLE', 'Page non trouvé !')
}
}
</script>
<style scoped>
.gradient {
height: 100vh;
background: rgb(52,152,219);
background: linear-gradient(45deg, rgba(52,152,219,1) 0%, rgba(39,174,96,1) 100%);
}
</style>

View file

@ -9074,10 +9074,10 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
vue-apitator@^0.0.16: vue-apitator@0.0.17:
version "0.0.16" version "0.0.17"
resolved "https://registry.yarnpkg.com/vue-apitator/-/vue-apitator-0.0.16.tgz#4c59f617aa692e7f0d3fd059424c3938a9666bea" resolved "https://registry.yarnpkg.com/vue-apitator/-/vue-apitator-0.0.17.tgz#3fcd925effa833956f07c63c57eaf72523c2dc0f"
integrity sha512-k0hQS8jR10avPTd28vPHjTV+H7aW2pfSz4O3gJ4D1z5J/ne4l+ygNAqQofOzgGL9bKaiojc9Guxr2f34fnUvYg== integrity sha512-xa3ChL3v2pq77x0KYQnSkrt5jSyYhvCGIKRsCQLAyvOqQqA1TPBYazBjQeEvP2FSFficIu9lJHlqp0gflkGy/A==
dependencies: dependencies:
axios "^0.19.0" axios "^0.19.0"
vue "^2.5.17" vue "^2.5.17"