Replace Vuetify with Bulma

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Remove vuetify and add Bulma

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
master
Thomas Citharel 4 years ago
parent 759a740625
commit 90fd0ff6b6
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773

@ -37,7 +37,7 @@ translations: ./$(OUTPUT_DIR)/translations.json
mkdir -p $(dir $@)
which gettext-extract
# Extract gettext strings from templates files and create a POT dictionary template.
gettext-extract --attribute v-translate --quiet --output $@ $(GETTEXT_HTML_SOURCES)
gettext-extract --attribute v-translate --quiet --parseScript false --output $@ $(GETTEXT_HTML_SOURCES)
# Extract gettext strings from JavaScript files.
xgettext --language=JavaScript --keyword=npgettext:1c,2,3 \
--from-code=utf-8 --join-existing --no-wrap \

936
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -8,7 +8,8 @@
"analyze-bundle": "npm run build -- --report-json && webpack-bundle-analyzer ../priv/static/report.json",
"dev": "vue-cli-service serve",
"test:e2e": "vue-cli-service test:e2e",
"test:unit": "vue-cli-service test:unit"
"test:unit": "vue-cli-service test:unit",
"prepare": "patch-package"
},
"dependencies": {
"apollo-absinthe-upload-link": "^1.4.0",
@ -17,6 +18,7 @@
"apollo-link": "^1.2.6",
"apollo-link-http": "^1.5.9",
"apollo-link-state": "^0.4.2",
"buefy": "^0.7.1",
"easygettext": "^2.7.0",
"graphql": "^14.1.1",
"graphql-tag": "^2.10.1",
@ -32,8 +34,6 @@
"vue-markdown": "^2.2.4",
"vue-property-decorator": "^7.2.0",
"vue-router": "^3.0.2",
"vuetify": "^1.3.9",
"vuetify-google-autocomplete": "^2.0.0-beta.5",
"vuex": "^3.0.1"
},
"devDependencies": {
@ -51,6 +51,7 @@
"chai": "^4.2.0",
"dotenv-webpack": "^1.5.7",
"node-sass": "^4.10.0",
"patch-package": "^5.1.1",
"sass-loader": "^7.1.0",
"tslint-config-airbnb": "^5.11.1",
"typescript": "^3.0.0",

@ -0,0 +1,41 @@
patch-package
--- a/node_modules/easygettext/src/extract-cli.js
+++ b/node_modules/easygettext/src/extract-cli.js
@@ -22,9 +22,12 @@ const endDelimiter = argv.endDelimiter === undefined ? constants.DEFAULT_DELIMIT
const extraAttribute = argv.attribute || false;
const extraFilter = argv.filter || false;
const filterPrefix = argv.filterPrefix || constants.DEFAULT_FILTER_PREFIX;
+const parseScript = argv.parseScript === undefined ? true : argv.parseScript === 'true';
if (!quietMode && (!files || files.length === 0)) {
- console.log('Usage:\n\tgettext-extract [--attribute EXTRA-ATTRIBUTE] [--filterPrefix FILTER-PREFIX] [--output OUTFILE] <FILES>');
+ console.log(
+ 'Usage:\n\tgettext-extract [--attribute EXTRA-ATTRIBUTE] [--filterPrefix FILTER-PREFIX] [--parseScript BOOLEAN] [--output OUTFILE] <FILES>',
+ );
process.exit(1);
}
@@ -54,7 +57,7 @@ const extractor = new extract.Extractor({
});
-files.forEach(function(filename) {
+files.forEach(function (filename) {
let file = filename;
const ext = file.split('.').pop();
if (ALLOWED_EXTENSIONS.indexOf(ext) === -1) {
@@ -63,9 +66,13 @@ files.forEach(function(filename) {
}
console.log(`[${PROGRAM_NAME}] extracting: '${filename}`);
try {
- let data = fs.readFileSync(file, {encoding: 'utf-8'}).toString();
+ let data = fs.readFileSync(file, { encoding: 'utf-8' }).toString();
extractor.parse(file, extract.preprocessTemplate(data, ext));
+ if (!parseScript) {
+ return;
+ }
+
if (ext !== 'js') {
data = extract.preprocessScriptTags(data, ext);
}

@ -1,17 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>mobilizon</title>
</head>
<body>
<noscript>
<strong>We're sorry but mobilizon doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<html class="has-navbar-fixed-top">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="//cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
<title>mobilizon</title>
</head>
<body>
<noscript>
<strong>We're sorry but mobilizon doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -1,152 +1,19 @@
<template>
<v-app id="libre-event">
<v-navigation-drawer
light
clipped
fixed
app
v-model="drawer"
enable-resize-watcher
>
<v-list dense>
<v-list-group
value="false"
>
<v-list-tile avatar v-if="actor" slot="activator">
<v-list-tile-avatar>
<img v-if="!actor.avatar"
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="actor.avatar"
>
</v-list-tile-avatar>
<v-list-tile-content @click="$router.push({name: 'Account', params: { name: actor.username }})">
<v-list-tile-title>{{ this.displayed_name }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile avatar v-if="actor">
<v-list-tile-avatar>
<img
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>Autre identité</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="$router.push({ name: 'Identities' })">
<v-list-tile-action>
<v-icon>group</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Identities</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list-group>
<template v-for="(item, i) in items" v-if="showMenuItem(item.role)">
<v-layout
row
v-if="item.heading"
align-center
:key="i"
>
<v-flex xs6>
<v-subheader v-if="item.heading">
{{ item.heading }}
</v-subheader>
</v-flex>
<v-flex xs6 class="text-xs-center">
<a href="#!" class="body-2 black--text">EDIT</a>
</v-flex>
</v-layout>
<v-list-tile v-bind:key="item.route" v-else @click="$router.push({ name: item.route })">
<v-list-tile-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
{{ item.text }}
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</template>
</v-list>
</v-navigation-drawer>
<NavBar v-bind="{toggleDrawer}"></NavBar>
<v-content>
<v-container fluid fill-height :class="{'px-0': $vuetify.breakpoint.xsOnly }">
<v-layout xs12>
<transition name="router">
<router-view></router-view>
</transition>
</v-layout>
</v-container>
</v-content>
<v-speed-dial
v-model="fab"
bottom
right
fixed
direction="top"
open-on-hover
transition="scale-transition"
v-if="currentUser"
>
<v-btn
slot="activator"
v-model="fab"
color="blue darken-2"
dark
fab
>
<v-icon>add</v-icon>
<v-icon>close</v-icon>
</v-btn>
<v-btn
fab
dark
small
color="pink"
@click="$router.push({name: 'CreateEvent'})"
>
<v-icon>event</v-icon>
</v-btn>
<v-btn
fab
dark
small
color="purple"
@click="$router.push({name: 'CreateGroup'})"
>
<v-icon>group</v-icon>
</v-btn>
</v-speed-dial>
<v-footer class="indigo" app>
<span
class="white--text"
v-translate="{
date: new Date().getFullYear(),
}">© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix & <a href="https://vuejs.org/">VueJS</a> & <a
href="https://www.vuetifyjs.com/">Vuetify</a> with some love and some weeks
</span>
</v-footer>
<v-snackbar
:timeout="error.timeout"
:error="true"
v-model="error.show"
>
{{ error.text }}
<v-btn dark flat @click.native="error.show = false">Close</v-btn>
</v-snackbar>
</v-app>
<div id="mobilizon">
<NavBar></NavBar>
<main class="container">
<router-view></router-view>
</main>
<footer class="footer">
<div class="content has-text-centered">
<span
v-translate="{
date: new Date().getFullYear(),
}"
>© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks</span>
</div>
</footer>
</div>
</template>
<script lang="ts">
@ -163,31 +30,40 @@ import { ICurrentUser } from '@/types/current-user.model'
}
},
components: {
NavBar,
},
NavBar
}
})
export default class App extends Vue {
drawer = false;
fab = false;
items = [
{
icon: 'poll', text: 'Events', route: 'EventList', role: null,
icon: "poll",
text: "Events",
route: "EventList",
role: null
},
{
icon: 'group', text: 'Groups', route: 'GroupList', role: null,
icon: "group",
text: "Groups",
route: "GroupList",
role: null
},
{
icon: 'content_copy', text: 'Categories', route: 'CategoryList', role: 'ROLE_ADMIN',
icon: "content_copy",
text: "Categories",
route: "CategoryList",
role: "ROLE_ADMIN"
},
{ icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
{ icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
{ icon: 'help', text: 'Help', role: null },
{ icon: 'phonelink', text: 'App downloads', role: null },
{ icon: "settings", text: "Settings", role: "ROLE_USER" },
{ icon: "chat_bubble", text: "Send feedback", role: "ROLE_USER" },
{ icon: "help", text: "Help", role: null },
{ icon: "phonelink", text: "App downloads", role: null }
];
error = {
timeout: 3000,
show: false,
text: '',
text: ""
};
currentUser!: ICurrentUser;
@ -199,21 +75,21 @@ export default class App extends Vue {
get displayed_name () {
// FIXME: load actor
return 'no implemented';
return "no implemented";
// return this.actor.display_name === null ? this.actor.username : this.actor.display_name
}
showMenuItem (elem) {
showMenuItem(elem) {
// FIXME: load actor
return false;
// return elem !== null && this.user && this.user.roles !== undefined ? this.user.roles.includes(elem) : true
}
getUser () {
getUser (): ICurrentUser|false {
return this.currentUser.id ? this.currentUser : false;
}
toggleDrawer () {
toggleDrawer() {
this.drawer = !this.drawer;
}
@ -236,16 +112,18 @@ export default class App extends Vue {
</script>
<style>
.router-enter-active, .router-leave-active {
transition-property: opacity;
transition-duration: .25s;
}
.router-enter-active,
.router-leave-active {
transition-property: opacity;
transition-duration: 0.25s;
}
.router-enter-active {
transition-delay: .25s;
}
.router-enter-active {
transition-delay: 0.25s;
}
.router-enter, .router-leave-active {
opacity: 0
}
.router-enter,
.router-leave-active {
opacity: 0;
}
</style>

@ -1,213 +0,0 @@
<template>
<v-layout row>
<v-flex xs12 sm6 offset-sm3>
<v-progress-circular v-if="$apollo.loading" indeterminate color="primary"></v-progress-circular>
<v-card v-if="actor">
<v-img :src="actor.banner || 'https://picsum.photos/400/'" height="300px">
<v-layout column class="media">
<v-card-title>
<v-btn icon @click="$router.go(-1)">
<v-icon>chevron_left</v-icon>
</v-btn>
<v-spacer></v-spacer>
<!-- <v-btn icon class="mr-3" v-if="actor.id === actor.id">
<v-icon>edit</v-icon>
</v-btn> -->
<v-menu bottom left>
<v-btn icon slot="activator">
<v-icon>more_vert</v-icon>
</v-btn>
<v-list>
<!-- <v-list-tile @click="logoutUser()" v-if="actor.id === actor.id">
<v-list-tile-title>User logout</v-list-tile-title>
</v-list-tile>
<v-list-tile @click="deleteAccount()" v-if="actor.id === actor.id">
<v-list-tile-title>Delete</v-list-tile-title>
</v-list-tile> -->
</v-list>
</v-menu>
</v-card-title>
<v-spacer></v-spacer>
<div class="text-xs-center">
<v-avatar size="125px">
<img v-if="!actor.avatarUrl"
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="actor.avatarUrl"
>
</v-avatar>
</div>
<v-container fluid grid-list-lg>
<v-layout row>
<v-flex xs7>
<div class="headline">{{ actor.name }}</div>
<div><span class="subheading">@{{ actor.preferredUsername }}<span v-if="actor.domain">@{{ actor.domain }}</span></span>
</div>
<v-card-text v-if="actor.description" v-html="actor.description"></v-card-text>
</v-flex>
</v-layout>
</v-container>
</v-layout>
</v-img>
<v-list three-line>
<v-list-tile>
<v-list-tile-action>
<v-icon color="indigo">phone</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>(323) 555-6789</v-list-tile-title>
<v-list-tile-sub-title>Work</v-list-tile-sub-title>
</v-list-tile-content>
<v-list-tile-action>
<v-icon dark>chat</v-icon>
</v-list-tile-action>
</v-list-tile>
<v-divider inset></v-divider>
<v-list-tile>
<v-list-tile-action>
<v-icon color="indigo">mail</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>ali_connors@example.com</v-list-tile-title>
<v-list-tile-sub-title>Work</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
<v-divider inset></v-divider>
<v-list-tile>
<v-list-tile-action>
<v-icon color="indigo">location_on</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>1400 Main Street</v-list-tile-title>
<v-list-tile-sub-title>Orlando, FL 79938</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
<v-container fluid grid-list-md v-if="actor.participatingEvents && actor.participatingEvents.length > 0">
<v-subheader>Participated at</v-subheader>
<v-layout row wrap>
<v-flex v-for="event in actor.participatingEvents" :key="event.id">
<v-card>
<v-img
class="black--text"
height="200px"
src="https://picsum.photos/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>
<v-flex xs12 align-end flexbox>
<span class="headline">{{ event.title }}</span>
</v-flex>
</v-layout>
</v-container>
</v-img>
<v-card-title>
<div>
<span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br>
<p>{{ event.description }}</p>
<p v-if="event.organizer">Organisé par
<router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link>
</p>
</div>
</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>favorite</v-icon>
</v-btn>
<v-btn icon>
<v-icon>bookmark</v-icon>
</v-btn>
<v-btn icon>
<v-icon>share</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
</v-container>
<v-container fluid grid-list-md v-if="actor.organizedEvents && actor.organizedEvents.length > 0">
<v-subheader>Organized events</v-subheader>
<v-layout row wrap>
<v-flex v-for="event in actor.organizedEvents" :key="event.id" md6>
<v-card>
<v-img
height="200px"
src="https://picsum.photos/400/200/"
/>
<v-card-title primary-title>
<div>
<router-link :to="{name: 'Event', params: {uuid: event.uuid}}">
<div class="headline">{{ event.title }}</div>
</router-link>
<span class="grey--text" v-html="nl2br(event.description)"></span>
</div>
</v-card-title>
<!-- <v-card-title>
<div>
<span class="grey--text" v-if="event.addressType === 'physical'">{{ event.startDate }} à {{ event.location }}</span><br>
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
</div>
</v-card-title> -->
<v-card-actions>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>favorite</v-icon>
</v-btn>
<v-btn icon>
<v-icon>bookmark</v-icon>
</v-btn>
<v-btn icon>
<v-icon>share</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-card>
</v-flex>
</v-layout>
</template>
<script lang="ts">
import { FETCH_ACTOR } from '@/graphql/actor';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
@Component({
apollo: {
actor: {
query: FETCH_ACTOR,
variables() {
return {
name: this.$route.params.name,
};
},
},
},
})
export default class Account extends Vue {
@Prop({ type: String, required: true }) name!: string;
actor = null;
// call again the method if the route changes
@Watch('$route')
onRouteChange() {
// this.fetchData()
}
logoutUser() {
// TODO : implement logout
this.$router.push({ name: 'Home' });
}
nl2br(text) {
return text.replace(/(?:\r\n|\r|\n)/g, '<br>');
}
};
</script>

@ -1,133 +0,0 @@
<template>
<v-layout row>
<v-flex xs12 sm6 offset-sm3>
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
<v-card v-if="!loading">
<v-toolbar dark color="primary">
<v-toolbar-title>Identities</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-list two-line>
<v-list-tile
v-for="actor in actors"
:key="actor.id"
avatar
@click="$router.push({ name: 'Account', params: { name: actor.username } })"
>
<v-list-tile-action>
<v-icon v-if="defaultActor === actor.username" color="pink">star</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-text="actor.username"></v-list-tile-title>
<v-list-tile-sub-title v-if="actor.display_name" v-text="actor.display_name"></v-list-tile-sub-title>
</v-list-tile-content>
<v-list-tile-avatar>
<img :src="actor.avatar">
</v-list-tile-avatar>
</v-list-tile>
</v-list>
<v-divider v-if="showForm"></v-divider>
<v-form v-if="showForm">
<v-text-field
label="Username"
required
type="text"
v-model="newActor.preferred_username"
:rules="[rules.required]"
:error="this.state.username.status"
:error-messages="this.state.username.msg"
:suffix="this.host()"
hint="You will be able to create more identities once registered"
persistent-hint
>
</v-text-field>
<v-textarea
name="input-7-1"
label="Profile description"
hint="Will be displayed publicly on your profile"
></v-textarea>
</v-form>
<v-btn
color="pink"
dark
absolute
bottom
right
fab
@click="toggleForm()"
>
<v-icon>{{ showForm ? 'check' : 'add' }}</v-icon>
</v-btn>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class Identities extends Vue {
actors = [];
newActor = {
preferred_username: '',
summary: '',
};
loading = true;
showForm = false;
rules = {
required: value => !!value || 'Required.',
};
state = {
username: {
status: false,
msg: [],
},
};
created() {
this.fetchData();
}
fetchData() {
// Implements eventFetch
// eventFetch('/user', this.$store)
// .then(response => response.json())
// .then((response) => {
// this.actors = response.data.actors;
// this.loading = false;
// });
}
sendData() {
this.loading = true;
this.showForm = false;
// Implements eventFetch
// eventFetch('/actors', this.$store, {
// method: 'POST',
// body: JSON.stringify({ actor: this.newActor }),
// })
// .then(response => response.json())
// .then((response) => {
// this.actors.push(response.data);
// this.loading = false;
// });
}
toggleForm() {
if (this.showForm === true) {
this.sendData();
} else {
this.showForm = true;
}
}
host() {
return `@${window.location.host}`;
}
}
</script>

@ -1,151 +0,0 @@
<template>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex xs12 sm8 md4>
<v-card class="elevation-12">
<v-toolbar dark color="primary">
<v-toolbar-title>Login</v-toolbar-title>
<v-spacer></v-spacer>
<v-tooltip bottom>
<v-btn
slot="activator"
:to="{ name: 'Register', params: { email: this.credentials.email, password: this.credentials.password } }"
>
<!-- <v-icon large>login</v-icon> -->
<span>Register</span>
</v-btn>
<span>Register</span>
</v-tooltip>
</v-toolbar>
<v-card-text>
<div class="text-xs-center">
<v-avatar size="80px">
<transition name="avatar">
<component :is="validEmail()" v-bind="{email: credentials.email}"></component>
<!-- <v-gravatar :email="credentials.email" default-img="mp" v-if="validEmail()"/>
<avatar v-else></avatar> -->
</transition>
</v-avatar>
</div>
<v-form @submit="loginAction" v-if="!validationSent">
<v-text-field
label="Email"
required
type="text"
v-model="credentials.email"
:rules="[rules.required, rules.email]"
>
</v-text-field>
<v-text-field
label="password"
required
type="password"
v-model="credentials.password"
:rules="[rules.required]"
>
</v-text-field>
<v-btn @click="loginAction" color="blue">Login</v-btn>
<router-link :to="{ name: 'SendPasswordReset', params: { email: credentials.email } }">Password forgotten ?</router-link>
</v-form>
<div v-else>
<h2>{{ $t('registration.form.validation_sent', { email: credentials.email }) }}</h2>
<b-alert show variant="info">{{ $t('registration.form.validation_sent_info') }}</b-alert>
</div>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script lang="ts">
import Gravatar from 'vue-gravatar';
import RegisterAvatar from './RegisterAvatar.vue';
import { Component, Prop, Vue } from 'vue-property-decorator';
import { LOGIN } from '@/graphql/auth';
import { validateEmailField, validateRequiredField } from '@/utils/validators';
import { saveUserData } from '@/utils/auth';
import { ILogin } from '@/types/login.model'
import { UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user'
import { onLogin } from '@/vue-apollo'
@Component({
components: {
'v-gravatar': Gravatar,
avatar: RegisterAvatar,
},
})
export default class Login extends Vue {
@Prop({ type: String, required: false, default: '' }) email!: string;
@Prop({ type: String, required: false, default: '' }) password!: string;
credentials = {
email: '',
password: '',
};
validationSent = false;
error = {
show: false,
text: '',
timeout: 3000,
field: {
email: false,
password: false,
},
};
rules = {
required: validateRequiredField,
email: validateEmailField
};
user: any;
beforeCreate() {
if (this.user) {
this.$router.push('/');
}
}
mounted() {
this.credentials.email = this.email;
this.credentials.password = this.password;
}
async loginAction(e: Event) {
e.preventDefault();
this.error.show = false;
try {
const result = await this.$apollo.mutate<{ login: ILogin }>({
mutation: LOGIN,
variables: {
email: this.credentials.email,
password: this.credentials.password,
},
});
saveUserData(result.data.login);
await this.$apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT,
variables: {
id: result.data.login.user.id,
email: this.credentials.email,
}
});
onLogin(this.$apollo);
this.$router.push({ name: 'Home' });
} catch (err) {
console.error(err);
this.error.show = true;
this.error.text = err.message;
}
}
validEmail() {
return this.rules.email(this.credentials.email) === true ? 'v-gravatar' : 'avatar';
}
}
</script>

@ -1,123 +0,0 @@
<template>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex xs12 sm8 md4>
<v-card class="elevation-12">
<v-toolbar dark color="primary">
<v-toolbar-title>Password Reset</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-alert type="error" :value="state.token.status === false">{{ state.token.msg }}</v-alert>
<v-form @submit="resetAction">
<v-text-field
label="Password"
type="password"
v-model="credentials.password"
required
:error="state.password.status"
:rules="[rules.required, rules.password_length]"
>
</v-text-field>
<v-text-field
label="Password (confirmation)"
type="password"
v-model="credentials.password_confirmation"
required
:rules="[rules.required, rules.password_length, rules.password_equal]"
:error="state.password_confirmation.status"
>
</v-text-field>
<v-btn type="submit" :disabled="!samePasswords" color="blue">Reset my password</v-btn>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { validateRequiredField } from '@/utils/validators';
import { RESET_PASSWORD } from '@/graphql/auth';
import { saveUserData } from '@/utils/auth';
import { ILogin } from '@/types/login.model'
@Component
export default class PasswordReset extends Vue {
@Prop({ type: String, required: true }) token!: string;
credentials = {
password: '',
password_confirmation: '',
};
error = {
show: false,
};
state = {
token: {
status: null,
msg: '',
},
password: {
status: null,
msg: '',
},
password_confirmation: {
status: null,
msg: '',
},
};
rules = {
password_length: value => value.length > 6 || 'Password must be at least 6 characters long',
required: validateRequiredField,
password_equal: value => value === this.credentials.password || 'Passwords must be the same',
};
get samePasswords() {
return this.rules.password_length(this.credentials.password) === true &&
this.credentials.password === this.credentials.password_confirmation;
}
async resetAction(e) {
this.resetState();
this.error.show = false;
e.preventDefault();
try {
const result = await this.$apollo.mutate<{ resetPassword: ILogin}>({
mutation: RESET_PASSWORD,
variables: {
password: this.credentials.password,
token: this.token,
},
});
saveUserData(result.data.resetPassword);
this.$router.push({ name: 'Home' });
} catch (err) {
console.error(err);
this.error.show = true;
}
}
resetState() {
this.state = {
token: {
status: null,
msg: '',
},
password_confirmation: {
status: null,
msg: '',
},
password: {
status: null,
msg: '',
},
};
}
};
</script>

@ -1,185 +0,0 @@
<template>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex xs12 sm8 md4>
<v-card class="elevation-12">
<v-toolbar dark color="primary">
<v-toolbar-title>Register</v-toolbar-title>
<v-spacer></v-spacer>
<v-tooltip bottom>
<v-btn
slot="activator"
:to="{ name: 'Login', params: { email, password } }"
>
<!-- <v-icon large>login</v-icon> -->
<span>Login</span>
</v-btn>
<span>Login</span>
</v-tooltip>
</v-toolbar>
<v-card-text>
<div class="text-xs-center">
<v-avatar size="80px">
<transition name="avatar">
<component :is="validEmail()" v-bind="{email}"></component>
<!-- <v-gravatar :email="credentials.email" default-img="mp" v-if="validEmail()"/>
<avatar v-else></avatar> -->
</transition>
</v-avatar>
</div>
<v-form @submit="submit()" v-if="!validationSent">
<v-text-field
label="Username"
required
type="text"
v-model="username"
:rules="[rules.required]"
:error="state.username.status"
:error-messages="state.username.msg"
:suffix="host()"
hint="You will be able to create more identities once registered"
persistent-hint
>
</v-text-field>
<v-text-field
label="Email"
required
type="email"
ref="email"
v-model="email"
:rules="[rules.required, rules.email]"
:error="state.email.status"
:error-messages="state.email.msg"
>
</v-text-field>
<v-text-field
label="Password"
required
:type="showPassword ? 'text' : 'password'"
v-model="password"
:rules="[rules.required, rules.password_length]"
:error="state.password.status"
:error-messages="state.password.msg"
:append-icon="showPassword ? 'visibility_off' : 'visibility'"
@click:append="showPassword = !showPassword"
>
</v-text-field>
<v-btn @click="submit()" color="primary">Register</v-btn>
<router-link :to="{ name: 'ResendConfirmation', params: { email }}">Didn't receive the instructions ?</router-link>
</v-form>
<div v-if="validationSent">
<h2>
<translate>A validation email was sent to %{email}</translate>
</h2>
<v-alert :value="true" type="info">
<translate>Before you can login, you need to click on the link inside it to validate your account</translate>
</v-alert>
</div>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script lang="ts">
import Gravatar from 'vue-gravatar';
import RegisterAvatar from './RegisterAvatar.vue';
import { CREATE_USER } from '@/graphql/user';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component({
components: {
'v-gravatar': Gravatar,
avatar: RegisterAvatar,
},
})
export default class Register extends Vue {
@Prop({ type: String, required: false, default: '' }) default_email!: string;
@Prop({ type: String, required: false, default: '' }) default_password!: string;
username = '';
email = this.default_email;
password = this.default_password;
error = {
show: false,
};
showPassword = false;
validationSent = false;
state = {
email: {
status: false,
msg: [],
},
username: {
status: false,
msg: [],
},
password: {
status: false,
msg: [],
},
};
rules = {
password_length: value => value.length > 6 || 'Password must be at least 6 characters long',
required: value => !!value || 'Required.',
email: (value: string) => value.includes('@') || 'Invalid e-mail.',
};
resetState() {
this.state = {
email: {
status: false,
msg: [],
},
username: {
status: false,
msg: [],
},
password: {
status: false,
msg: [],
},
};
}
host() {
return `@${window.location.host}`;
}
validEmail() {
return this.rules.email(this.email) === true ? 'v-gravatar' : 'avatar';
}
async submit() {
try {
await this.$apollo.mutate({
mutation: CREATE_USER,
variables: {
email: this.email,
password: this.password,
username: this.username,
},
});
this.validationSent = true;
} catch (error) {
console.error(error);
}
}
};
</script>
<style lang="scss">
.avatar-enter-active {
transition: opacity 1s ease;
}
.avatar-enter, .avatar-leave-to {
opacity: 0;
}
.avatar-leave {
display: none;
}
</style>

@ -1,12 +0,0 @@
<template>
<img class="img-circle elevation-7 mb-1" src="@/assets/profile.svg">
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class RegisterAvatar extends Vue {
}
</script>

@ -1,82 +0,0 @@
<template>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex xs12 sm8 md4>
<v-card class="elevation-12">
<v-toolbar dark color="primary">
<v-toolbar-title>Resend Instructions</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form @submit="resendConfirmationAction" v-if="!validationSent">
<v-text-field
label="Email"
type="email"
v-model="credentials.email"
required
:state="state.email.status"
:rules="[rules.required, rules.email]"
>
</v-text-field>
<v-btn type="submit" color="blue">Send instructions again</v-btn>
</v-form>
<div v-else>
<h2>Validation email sent to {{ credentials.email }}</h2>
<v-alert :value="true" type="info">Please check you spam folder if you didn't receive the email.</v-alert>
</div>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { validateEmailField, validateRequiredField } from '@/utils/validators';
import { RESEND_CONFIRMATION_EMAIL } from '@/graphql/auth';
@Component
export default class ResendConfirmation extends Vue {
@Prop({ type: String, required: false, default: '' }) email!: string;
credentials = {
email: '',
};
validationSent = false;
error = false;
state = {
email: {
status: null,
msg: '',
},
};
rules = {
required: validateRequiredField,
email: validateEmailField,
};
mounted() {
this.credentials.email = this.email;
}
async resendConfirmationAction(e) {
e.preventDefault();
this.error = false;
try {
await this.$apollo.mutate({
mutation: RESEND_CONFIRMATION_EMAIL,
variables: {
email: this.credentials.email,
},
});
} catch (err) {
console.error(err);
this.error = true;
} finally {
this.validationSent = true;
}
}
};
</script>

@ -1,92 +0,0 @@
<template>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex xs12 sm8 md4>
<v-card class="elevation-12">
<v-toolbar dark color="primary">
<v-toolbar-title>Password Reset</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form @submit="resendConfirmationAction" v-if="!validationSent">
<v-text-field
label="Email"
type="email"
v-model="credentials.email"
required
:state="state.email.status"
:rules="[rules.required, rules.email]"
>
</v-text-field>
<v-btn type="submit" color="blue">Reset my password</v-btn>
</v-form>