Replaced sweet alert with custom modal

This commit is contained in:
Roman Cervantes 2019-02-05 00:31:40 -07:00
parent b6c0547f24
commit a13321c1ac
14 changed files with 387 additions and 923 deletions

View file

@ -27,7 +27,6 @@
"raven-js": "^3.27.0",
"sass-loader": "^7.0.1",
"sw-precache-webpack-plugin": "^0.11.5",
"sweetalert2": "^7.29.1",
"v-tooltip": "^2.0.0-rc.33",
"vue": "^2.5.2",
"vue-analytics": "^5.16.0",

View file

@ -1,3 +1,4 @@
<!-- eslint-disable max-len -->
<template lang="html">
<div :class="['list', { dark: darkModeEnabled }]">
<div class="list-header">
@ -81,10 +82,27 @@
<i class="fas fa-sort-numeric-up" />
</button>
<modal
v-if="games && games.length"
ref="addList"
:message="`This list contains ${games.length} games, all games will be deleted as well.`"
title="Are you sure?"
:action-text="$t('list.delete')"
@action="deleteList"
>
<button
:class="['small accent', { hollow: darkModeEnabled }]"
:title="$t('list.delete')"
@click="remove"
>
<i class="far fa-trash-alt" />
</button>
</modal>
<button
v-else
:class="['small accent', { hollow: darkModeEnabled }]"
:title="$t('list.delete')"
@click="deleteList"
>
<i class="far fa-trash-alt" />
</button>
@ -104,6 +122,7 @@
<script>
import draggable from 'vuedraggable';
import ListNameEdit from '@/components/GameBoard/ListNameEdit';
import Modal from '@/components/Modal/Modal';
import GameCard from '@/components/GameCard/GameCard';
import GameSearch from '@/components/GameSearch/GameSearch';
import { mapState, mapGetters } from 'vuex';
@ -118,6 +137,7 @@ db.settings({
export default {
components: {
Modal,
GameCard,
GameSearch,
ListNameEdit,
@ -162,6 +182,18 @@ export default {
},
methods: {
deleteList() {
this.$store.commit('REMOVE_LIST', this.listIndex);
db.collection('lists').doc(this.user.uid).set(this.gameLists, { merge: true })
.then(() => {
this.$bus.$emit('TOAST', { message: 'List deleted' });
})
.catch(() => {
this.$bus.$emit('TOAST', { message: 'Authentication error', type: 'error' });
});
},
moveList(from, to) {
this.$store.commit('MOVE_LIST', { from, to });
this.updateLists();
@ -207,10 +239,6 @@ export default {
end() {
this.$emit('end');
},
remove() {
this.$emit('remove');
},
},
};
</script>

View file

@ -1,12 +1,46 @@
<template lang="html">
<div class="edit-list">
<form @submit.prevent="addList" v-if="show">
<div class="list-options">
<div class="actions">
<modal
ref="addList"
:action-text="$t('global.create')"
message="Pick an option below"
:title="$t('list.add')"
:action-disabled="isDuplicate || !newListName"
@action="addList"
@close="reset"
@open="focusField"
>
<button
class="small info"
:title="$t('list.add')"
>
<i class="fas fa-plus" />
</button>
<form slot="content">
<div class="suggestions">
<button
class="small primary hollow"
v-for="suggestion in listNameSuggestions"
:key="suggestion"
type="button"
:disabled="listNames.includes(suggestion.toLowerCase())"
@click="addList(suggestion)"
>
{{ suggestion }}
</button>
</div>
<p>Or enter your own list name</p>
<input
v-model="newListName"
type="text"
ref="newListName"
autofocus
required
:placeholder="$t('list.name')"
:placeholder="$t('list.input')"
/>
<panel
@ -14,70 +48,60 @@
v-if="isDuplicate"
v-html="errorMessage"
/>
<div>
<button
type="submit"
class="small primary"
v-if="!isDuplicate"
:disabled="!newListName.length"
>
{{ $t('global.create') }}
</button>
<button
class="small info hollow"
type="button"
v-if="list"
@click="reset"
>
{{ $t('global.cancel') }}
</button>
</div>
</form>
</modal>
<div class="actions" v-else>
<button
class="small info"
:title="$t('list.add')"
@click="toggleAddList"
<modal
:action-text="`Delete forever`"
:message="`Your ${platform.name} collection will be deleted forever.`"
title="Are you sure?"
@action="deletePlatform"
>
<i class="fas fa-plus" />
</button>
<button
class="small info"
:title="$t('list.add')"
@click="promptDeletePlatform"
:title="$t('list.delete')"
>
<i class="far fa-trash-alt" />
</button>
</modal>
<button
class="small info"
@click="showShareModal"
title="Share"
<modal
title="Share your list"
message="Use the following URL to share this list."
close-text="OK"
>
<button class="small info" title="Share">
<i class="fas fa-share-alt" />
</button>
<div slot="content">
<input type="text" :value="shareUrl">
</div>
</modal>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import { swal } from '@/shared/modals';
import Panel from '@/components/Panel/Panel';
import Modal from '@/components/Modal/Modal';
export default {
components: {
Modal,
Panel,
},
data() {
return {
show: false,
newListName: '',
listNameSuggestions: [
'Owned',
'Wishlist',
'Currently Playing',
'Completed',
],
};
},
@ -94,12 +118,18 @@ export default {
isDuplicate() {
const newListName = this.newListName.toLowerCase();
// eslint-disable-next-line
return this.list ?
this.list.filter(({ name }) => name.toLowerCase() === newListName).length > 0
: false;
},
listNames() {
return this.list ?
this.list.map(({ name }) => name.toLowerCase())
: [];
},
shareUrl() {
const url = process.env.NODE_ENV === 'development'
? 'http://localhost:5000'
@ -109,70 +139,36 @@ export default {
},
},
watch: {
show() {
mounted() {
if (!this.list) {
this.$refs.addList.open();
}
},
methods: {
focusField() {
this.$nextTick(() => {
if (this.$refs.newListName) {
this.$refs.newListName.focus();
}
});
},
},
mounted() {
if (!this.list) {
this.toggleAddList();
}
},
methods: {
toggleAddList() {
if (!this.show) {
this.$nextTick(() => {
this.$emit('scroll');
});
}
this.show = !this.show;
},
showShareModal() {
swal({
titleText: 'Share your list',
html: 'Use the following URL to share this list.',
input: 'url',
inputValue: this.shareUrl,
showConfirmButton: false,
});
},
promptDeletePlatform() {
swal({
title: 'Are you sure?',
text: `Your ${this.platform.name} collection will be deleted forever.`,
type: 'warning',
showCancelButton: true,
confirmButtonClass: 'error',
cancelButtonClass: 'accent',
buttonsStyling: false,
confirmButtonText: `Yes, delete ${this.platform.name} collection`,
}).then(({ value }) => {
if (value) {
deletePlatform() {
this.$store.commit('REMOVE_PLATFORM');
this.$router.push({ name: 'platforms' });
this.$emit('update', true);
}
});
},
addList() {
this.$store.commit('ADD_LIST', this.newListName);
addList(suggestion) {
const listName = suggestion || this.newListName;
this.$store.commit('ADD_LIST', listName);
this.$ga.event({
eventCategory: 'list',
eventAction: 'add',
eventLabel: 'listAdded',
eventValue: this.newListName,
eventValue: listName,
});
this.$emit('update');
@ -180,10 +176,17 @@ export default {
this.reset();
this.$bus.$emit('TOAST', { message: 'List added' });
this.$nextTick(() => {
this.$emit('scroll');
});
if (suggestion) {
this.$refs.addList.close();
}
},
reset() {
this.show = false;
this.newListName = '';
},
},
@ -193,26 +196,10 @@ export default {
<style lang="scss" rel="stylesheet/scss" scoped>
@import "~styles/styles.scss";
.edit-list {
.list-options {
padding-right: $gp;
}
form {
border-radius: $border-radius;
background: $color-light-gray;
padding: $gp / 2;
display: flex;
flex-direction: column;
input {
width: 284px;
@media($small) {
width: 200px;
}
}
}
.actions {
display: grid;
grid-gap: $gp;
@ -231,10 +218,9 @@ export default {
padding: $gp / 2;
border-radius: $border-radius;
}
</style>
<style lang="scss" rel="stylesheet/scss">
.swal2-input {
font-size: 10px !important;
.suggestions {
display: grid;
grid-gap: $gp;
}
</style>

View file

@ -0,0 +1,139 @@
<template lang="html">
<div>
<div @click="open">
<slot />
</div>
<div :class="['modal', { show }]" @click="close">
<div class="content" @click.stop>
<i class="close fas fa-times" @click="close" />
<h2 v-if="title">{{ title }}</h2>
<p v-if="message">{{ message }}</p>
<slot name="content" />
<div :class="{ actions: actionText }">
<button class="small info" @click="close" v-if="showClose">
{{ closeText }}
</button>
<button
v-if="actionText"
class="small primary action"
:disabled="actionDisabled"
@click="handleAction"
>
{{ actionText }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
actionText: String,
title: String,
message: String,
showClose: {
type: Boolean,
default: true,
},
closeText: {
type: String,
default: 'Cancel',
},
actionDisabled: {
type: Boolean,
default: false,
},
},
data() {
return {
show: false,
};
},
methods: {
open() {
this.show = true;
this.$emit('open');
},
handleAction() {
this.$emit('action');
this.close();
},
close() {
this.$emit('close');
this.show = false;
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
@import "~styles/styles.scss";
.modal {
width: 100%;
position: fixed;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: .1;
height: 100%;
transition: all 100ms linear;
visibility: hidden;
cursor: pointer;
&.show {
visibility: visible;
transition: all 100ms linear;
opacity: 1;
}
}
.content {
position: relative;
background: $color-white;
width: 300px;
height: auto;
padding: $gp;
border-radius: $border-radius;
cursor: default;
}
.close {
padding: $gp $gp $gp / 2;
color: $color-gray;
position: absolute;
top: 0;
right: 0;
cursor: pointer;
transition: color 100ms linear;
&:hover {
transition: color 100ms linear;
color: $color-dark-gray;
}
}
.actions {
display: grid;
grid-gap: $gp;
grid-template-columns: auto auto;
}
h2 {
margin: 0;
}
</style>

View file

@ -4,34 +4,40 @@
tag="button"
class="logo"
:to="{ name: homeRoute }"
v-if="!isAuthRoute"
>
<img src='/static/gamebrary-logo.png' />
GAMEBRARY
</router-link>
<router-link
v-if="showSettings"
tag="button"
:to="{ name: 'settings' }"
<modal
title="Settings"
:show-close="false"
v-if="user"
>
<button>
<i class="fas fa-cog" />
</router-link>
</button>
<settings slot="content" v-if="settings" />
</modal>
</nav>
</template>
<script>
import Settings from '@/components/Settings/Settings';
import Modal from '@/components/Modal/Modal';
import { mapState, mapGetters } from 'vuex';
export default {
computed: {
...mapState(['user', 'platform']),
...mapGetters(['darkModeEnabled']),
showSettings() {
return this.$route.name !== 'settings' && this.user;
components: {
Settings,
Modal,
},
computed: {
...mapState(['user', 'platform', 'settings']),
...mapGetters(['darkModeEnabled']),
isAuthRoute() {
return this.$route.name === 'auth';
},

View file

@ -1,12 +1,8 @@
<template lang="html">
<div
class="settings"
v-if="user && localSettings" :class="{ dark: darkModeEnabled }"
:class="{ dark: darkModeEnabled }"
>
<section>
<h3>{{ $t('settings.title') }}</h3>
</section>
<section>
<div class="profile">
<gravatar :email="user.email" />
@ -64,13 +60,21 @@
{{ $t('settings.signOut') }}
</button>
<button @click="promptDelete" class="error hollow small">
<modal
message="Your account data will be deleted forever."
title="Are you sure?"
:action-text="$t('settings.deleteAccount')"
@action="deleteAccount"
>
<button class="error hollow small">
<i class="fas fa-exclamation-triangle" />
{{ $t('settings.deleteAccount') }}
</button>
</modal>
</section>
<section>
<section class="support">
<a href="https://www.paypal.me/RomanCervantes/5" class="link small" target="_blank">
<i class="fas fa-donate" />
{{ $t('settings.donate') }}
@ -90,6 +94,7 @@
<div class="copyright">
<p>
<i class="far fa-copyright" /> 2018 Gamebrary.
<br>
<i class="fas fa-code" />
{{ $t('global.with') }}
<i class="fas fa-heart" /> {{ $t('global.by') }}
@ -99,7 +104,6 @@
</template>
<script>
import { debounce } from 'lodash';
import { mapState, mapGetters } from 'vuex';
import firebase from 'firebase/app';
import 'firebase/firestore';
@ -107,19 +111,14 @@ import 'firebase/auth';
import Gravatar from 'vue-gravatar';
import Panel from '@/components/Panel/Panel';
import ToggleSwitch from '@/components/ToggleSwitch/ToggleSwitch';
import { swal } from '@/shared/modals';
import Modal from '@/components/Modal/Modal';
import moment from 'moment';
const db = firebase.firestore();
db.settings({
timestampsInSnapshots: true,
});
export default {
components: {
Panel,
ToggleSwitch,
Modal,
Gravatar,
},
@ -136,6 +135,12 @@ export default {
dateJoined() {
return moment(this.user.dateJoined).format('LL');
},
exitUrl() {
return process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: 'https://gamebrary.com';
},
},
watch: {
@ -159,32 +164,19 @@ export default {
this.save();
},
promptDelete() {
swal({
title: 'Are you sure?',
text: 'Your account data will be deleted forever.',
type: 'warning',
showCancelButton: true,
confirmButtonClass: 'error',
cancelButtonClass: 'accent',
buttonsStyling: false,
confirmButtonText: 'Yes, delete forever!',
}).then(({ value }) => {
if (value) {
this.deleteAccount();
}
});
},
deleteAccount() {
// TODO: use async/await
const db = firebase.firestore();
db.settings({
timestampsInSnapshots: true,
});
db.collection('settings').doc(this.user.uid).delete()
.then(() => {
db.collection('lists').doc(this.user.uid).delete()
.then(() => {
this.$bus.$emit('TOAST', { message: 'Account deleted' });
this.$store.commit('CLEAR_SESSION');
this.$router.push({ name: 'home' });
this.exit();
})
.catch(() => {
this.$bus.$emit('TOAST', { message: 'Authentication error', type: 'error' });
@ -195,33 +187,24 @@ export default {
});
},
exit() {
this.$store.commit('CLEAR_SESSION');
window.location.href = this.exitUrl;
},
signOut() {
firebase.auth().signOut()
.then(() => {
this.$store.commit('CLEAR_SESSION');
const exitUrl = process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: 'https://gamebrary.com';
window.location.href = exitUrl;
this.exit();
})
.catch((error) => {
this.$bus.$emit('TOAST', { message: error, type: 'error' });
});
},
save: debounce(
// eslint-disable-next-line
function() {
db.collection('settings').doc(this.user.uid).set(this.localSettings, { merge: true })
.then(() => {
this.$store.commit('SET_SETTINGS', this.localSettings);
this.$bus.$emit('TOAST', { message: 'Settings saved' });
})
.catch(() => {
this.$bus.$emit('TOAST', { message: 'There was an error saving your settings', type: 'error' });
});
}, 500),
save() {
this.$bus.$emit('SAVE_SETTINGS', this.localSettings);
},
},
};
</script>
@ -230,12 +213,10 @@ export default {
@import "~styles/styles.scss";
.settings {
background: $color-white;
color: $color-dark-gray;
display: flex;
align-items: center;
flex-direction: column;
min-height: calc(100vh - #{$navHeight});
.profile {
display: flex;
@ -271,6 +252,12 @@ export default {
display: flex;
align-items: center;
&.support {
flex-direction: column;
font-size: 12px;
padding: 0;
}
@media($small) {
padding: $gp;
}
@ -299,11 +286,9 @@ export default {
}
&.dark {
background: $color-darkest-gray;
section {
border-bottom: 1px solid $color-gray;
color: $color-gray;
// border-bottom: 1px solid $color-gray;
// color: $color-gray;
}
}
}

View file

@ -16,7 +16,6 @@
v-if="list && !loading"
v-for="(list, listIndex) in gameLists[platform.code]"
@end="dragEnd"
@remove="tryDelete(listIndex)"
/>
<onboard v-if="!list" />
@ -34,7 +33,6 @@ import ListOptions from '@/components/Lists/ListOptions';
import GameBoardPlaceholder from '@/components/GameBoard/GameBoardPlaceholder';
import Onboard from '@/components/GameBoard/Onboard';
import Panel from '@/components/Panel/Panel';
import { swal } from '@/shared/modals';
import List from '@/components/GameBoard/List';
import draggable from 'vuedraggable';
import { mapState, mapGetters } from 'vuex';
@ -63,8 +61,6 @@ export default {
draggingId: null,
loading: false,
gameData: null,
activeList: null,
showDeleteConfirm: false,
listDraggableOptions: {
animation: 500,
handle: '.list-drag-handle',
@ -87,6 +83,8 @@ export default {
},
mounted() {
this.$store.commit('CLEAR_ACTIVE_LIST');
if (this.platform) {
this.loadGameData();
@ -95,9 +93,7 @@ export default {
: 'Gamebrary';
} else {
// eslint-disable-next-line
if (this.user) {
this.$router.push({ name: 'platforms' });
} else {
if (!this.user) {
this.$router.push({ name: 'auth' });
}
}
@ -111,37 +107,6 @@ export default {
});
},
tryDelete(index) {
const hasGames = this.list[index].games.length > 0;
if (hasGames) {
this.showDeleteConfirm = true;
this.activeList = index;
swal({
title: 'Are you sure?',
text: 'This lists contains games, all games will be deleted as well.',
showCancelButton: true,
buttonsStyling: false,
confirmButtonClass: 'primary small',
cancelButtonClass: 'small',
confirmButtonText: 'Delete',
}).then(({ value }) => {
if (value) {
this.deleteList(this.activeList);
}
});
} else {
this.deleteList(index);
}
},
deleteList(index) {
this.$store.commit('REMOVE_LIST', index);
this.updateLists();
this.$bus.$emit('TOAST', { message: 'List deleted' });
},
dragEnd() {
this.dragging = false;
this.draggingId = null;
@ -174,22 +139,11 @@ export default {
this.loading = true;
this.$store.dispatch('LOAD_GAMES', gameList)
.catch(() => {
swal({
title: 'Uh no!',
text: 'There was an error loading your game data',
type: 'error',
showCancelButton: true,
confirmButtonClass: 'primary',
confirmButtonText: 'Retry',
}).then(({ value }) => {
if (value) {
this.loadGameData();
}
});
})
.finally(() => {
.then(() => {
this.loading = false;
})
.catch(() => {
this.$bus.$emit('TOAST', { message: 'Error loading game', type: 'error' });
});
}
}

View file

@ -53,7 +53,6 @@
<script>
import { mapState, mapGetters } from 'vuex';
import { swal } from '@/shared/modals';
import GameHeader from '@/components/GameDetail/GameHeader';
import GameScreenshots from '@/components/GameDetail/GameScreenshots';
import GameVideos from '@/components/GameDetail/GameVideos';
@ -83,6 +82,8 @@ export default {
},
mounted() {
this.$store.commit('CLEAR_ACTIVE_GAME');
if (this.$route.params.id) {
this.loadGame(this.$route.params.id);
} else {
@ -90,10 +91,6 @@ export default {
}
},
destroyed() {
this.$store.commit('CLEAR_ACTIVE_GAME');
},
methods: {
getImageUrl(cloudinaryId) {
return cloudinaryId
@ -102,7 +99,7 @@ export default {
},
goHome() {
this.$router.push({ name: 'home' });
this.$router.push({ name: 'game-board' });
},
loadGame(gameId) {
@ -118,20 +115,8 @@ export default {
document.title = `${this.game.name} (${this.platform.name}) - Gamebrary`;
})
.catch(() => {
swal({
title: 'Uh no!',
text: 'There was an error loading game details',
type: 'error',
showCancelButton: true,
confirmButtonClass: 'primary',
confirmButtonText: 'Retry',
}).then(({ value }) => {
if (value) {
this.loadGame();
} else {
this.$bus.$emit('TOAST', { message: 'Error loading game', type: 'error' });
this.goHome();
}
});
});
},
},

View file

@ -34,7 +34,6 @@
<script>
import firebase from 'firebase/app';
import GameBoardPlaceholder from '@/components/GameBoard/GameBoardPlaceholder';
import { swal } from '@/shared/modals';
import { mapState } from 'vuex';
import 'firebase/firestore';
@ -79,7 +78,7 @@ export default {
})
.catch(() => {
this.loading = false;
this.handleError();
this.$bus.$emit('TOAST', { message: 'Error loading data', type: 'error' });
});
},
@ -108,23 +107,6 @@ export default {
this.loading = false;
}
},
handleError() {
swal({
title: 'Uh no!',
text: 'There was an error loading game data',
type: 'error',
showCancelButton: true,
confirmButtonClass: 'primary',
confirmButtonText: 'Retry',
}).then(({ value }) => {
if (value) {
this.load();
} else {
this.$router.push({ name: 'home' });
}
});
},
},
};
</script>

View file

@ -5,7 +5,6 @@ import SessionExpired from '@/pages/SessionExpired/SessionExpired';
import GameDetail from '@/pages/GameDetail/GameDetail';
import GameBoard from '@/pages/GameBoard/GameBoard';
import Auth from '@/pages/Auth/Auth';
import Settings from '@/pages/Settings/Settings';
import Platforms from '@/pages/Platforms/Platforms';
Vue.use(Router);
@ -30,14 +29,6 @@ export default new Router({
title: 'Platforms',
},
},
{
name: 'settings',
path: '/settings',
component: Settings,
meta: {
title: 'Settings',
},
},
{
path: '/session-expired',
name: 'sessionExpired',

View file

@ -1,5 +0,0 @@
import swal from 'sweetalert2/dist/sweetalert2';
import 'sweetalert2/src/sweetalert2.scss';
/* eslint-disable */
export { swal };

View file

@ -2,4 +2,3 @@
@import "_measurements";
@import "_buttons";
@import "_inputs";
@import "_sweetalert";

View file

@ -1,16 +0,0 @@
.swal2-popup {
.swal2-title {
font-size: initial;
}
.swal2-content {
// display: flex;
// flex-direction: column;
// align-items: center;
font-size: initial;
}
.swal2-input {
text-align: center;
}
}

617
yarn.lock

File diff suppressed because it is too large Load diff