Initial commit.

This commit is contained in:
Ahmed Bouhuolia
2019-08-20 00:32:41 +02:00
commit cb8c294d74
61 changed files with 20779 additions and 0 deletions

11
client/src/App.vue Normal file
View File

@@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
};
</script>

BIN
client/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,113 @@
<template>
<div class="hello">
<h1>\{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
<li>
<a
href="https://vuejs.org"
target="_blank"
>
Core Docs
</a>
</li>
<li>
<a
href="https://forum.vuejs.org"
target="_blank"
>
Forum
</a>
</li>
<li>
<a
href="https://chat.vuejs.org"
target="_blank"
>
Community Chat
</a>
</li>
<li>
<a
href="https://twitter.com/vuejs"
target="_blank"
>
Twitter
</a>
</li>
<br>
<li>
<a
href="http://vuejs-templates.github.io/webpack/"
target="_blank"
>
Docs for This Template
</a>
</li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li>
<a
href="http://router.vuejs.org/"
target="_blank"
>
vue-router
</a>
</li>
<li>
<a
href="http://vuex.vuejs.org/"
target="_blank"
>
vuex
</a>
</li>
<li>
<a
href="http://vue-loader.vuejs.org/"
target="_blank"
>
vue-loader
</a>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
>
awesome-vue
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Vue.js App',
};
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -0,0 +1,5 @@
export default {
login: 'Login',
password: 'Password',
username_password: 'Username or Email',
};

21
client/src/main.js Normal file
View File

@@ -0,0 +1,21 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import App from '@/App';
import router from '@/router';
import store from '@/store';
// Plugins
import '@/plugins/i18n';
Vue.config.productionTip = false;
Vue.use(ElementUI);
const app = new Vue({
el: '#app',
render: h => h(App),
router,
store,
});
export default app;

View File

@@ -0,0 +1,44 @@
<template>
<div class="auth-page">
<div class="auth-page__logo">
</div>
<div class="auth-page__card">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'auth-warpper',
beforeRouteEnter(to, from, next) {
document.body.classList.add('page-auth');
next();
},
beforeRouteLeave(to, from, next) {
document.body.classList.remove('page-auth');
next();
},
};
</script>
<style>
body.page-auth{
background: red;
}
.auth-page{
width: 600px;
&__logo{
}
&__card{
background: #fff;
padding: 10px;
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<div class="login">
<el-form ref="form" class="form-container">
<el-form-item :label="$t('username_password')" prop="title">
<el-input v-model="form.crediential" :maxlength="100" name="name" required />
</el-form-item>
<el-form-item :label="$t('password')">
<el-input v-model="form.password" name="password" />
</el-form-item>
<el-button type="primary">{{ $t('login') }}</el-button>
<el-link>{{ $t('forget_your_password') }}</el-link>
</el-form>
</div>
</template>
<script>
export default {
name: 'login',
data() {
return {
form: {
username: '',
password: '',
},
};
},
methods: {
},
};
</script>

View File

@@ -0,0 +1,4 @@
<template>
</template>

View File

@@ -0,0 +1,33 @@
<template>
<div id="dashboard" class="dashboard">
<DashboardSidebar />
<div class="dashboard__content">
<DashboardTopbar></DashboardTopbar>
<div class="dashboard__content-inner">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
import DashboardSidebar from '@/views/Sidebar';
import DashboardTopbar from '@/views/Topbar';
export default {
name: 'dashboard',
components: {
DashboardTopbar,
DashboardSidebar,
},
};
</script>
<style lang="scss">
#dashboard{
display: flex;
flex-direction: row;
}
</style>

View File

@@ -0,0 +1,9 @@
<template>
<div>Home</div>
</template>
<script>
export default {
name: 'home',
};
</script>

View File

@@ -0,0 +1,28 @@
import Vue from 'vue';
export default {
get(resource, params) {
return Vue.axios.get(`api/${resource}`, params).catch((error) => {
throw new Error(`[Moosher] ApiService ${error}`);
});
},
post(resource, params) {
return Vue.axios.post(`api/${resource}`, params);
},
update(resource, slug, params) {
return Vue.axios.put(`api/${resource}/${slug}`, params);
},
put(resource, params) {
return Vue.axios.put(`api/${resource}`, params);
},
delete(resource) {
return Vue.axios.delete(`api/${resource}`).catch((error) => {
throw new Error(`[Moosher] ApiService ${error}`);
});
}
};

View File

@@ -0,0 +1,59 @@
import axios from 'axios';
import store from '~/store';
import swal from 'sweetalert2'
import Vue from 'vue';
import VueAxios from 'vue-axios';
import router from '~/routes';
// Set config defaults when creating the instance
const http = axios.create();
// request interceptor.
http.interceptors.request.use((request) => {
const token = store.getters.authToken;
const locale = Vue.i18n.locale();
if (token) {
request.headers.common['x-access-token'] = token;
}
if (locale) {
request.headers.common['Accept-Language'] = locale;
}
return request;
});
// response interceptor
http.interceptors.response.use(response => response, (error) => {
const { status } = error.response;
if (status >= 500) {
swal({
type: 'error',
title: Vue.i18n.translate('error_alert_title'),
text: Vue.i18n.translate('error_alert_text'),
reverseButtons: true,
confirmButtonText: Vue.i18n.translate('ok'),
cancelButtonText: Vue.i18n.translate('cancel')
});
}
if (status === 401) {
swal({
type: 'warning',
title: Vue.i18n.translate('token_expired_alert_title'),
text: Vue.i18n.translate('token_expired_alert_text'),
reverseButtons: true,
confirmButtonText: Vue.i18n.translate('ok'),
cancelButtonText: Vue.i18n.translate('cancel')
}).then(() => {
store.commit('removeToken');
router.push({ name: 'login' });
});
}
return Promise.reject(error)
});
Vue.use(VueAxios, http);
export default http;

View File

@@ -0,0 +1,29 @@
import Vue from 'vue';
const defaultLocale = 'en';
const loadedLangauges = [];
const currentLocale = Vue.i18n.locale();
function setLanguage(locale, messages) {
const localeMessages = messages.default;
Vue.i18n.add(locale, { ...localeMessages });
loadedLangauges.push(locale);
Vue.i18n.set(locale);
}
async function loadAsyncLanguage(locale) {
if (locale !== currentLocale) {
if (!loadedLangauges.includes(locale)) {
const messages = await import(/* webpackChunkName: "lang-[request]" */ `@/lang/${locale}/app`);
setLanguage(locale, messages);
}
}
}
(async () => {
await loadAsyncLanguage(defaultLocale);
})();
export default loadAsyncLanguage;

View File

@@ -0,0 +1,43 @@
import Vue from 'vue';
import Router from 'vue-router';
import store from '@/store';
import routes from './routes';
Vue.use(Router);
const router = new Router({
mode: 'hash',
routes: [
...routes,
],
});
router.beforeEach((to, from, next) => {
const isAuthenticated = store.getters.authCheck;
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!isAuthenticated) {
next({
name: 'login',
params: { nextUrl: to.fullPath },
});
} else {
next();
}
} else if (to.matched.some(record => record.meta.guest)) {
if (isAuthenticated) {
next({
name: 'dashboard.name',
params: {
nextUrl: to.fullPath,
},
});
} else {
next();
}
} else {
next();
}
});
export default router;

View File

@@ -0,0 +1,33 @@
const routes = [
{
name: 'auth',
path: '/',
component: () => import(/* webpackChunkName: "auth" */
'@/pages/Auth/Auth.vue'),
children: [
{
name: 'authLogin',
path: '/login',
component: () => import(/* webpackChunkName: "login" */
'@/pages/Auth/Login.vue'),
},
],
},
{
name: 'dashboard',
path: '/dashboard',
component: () => import(/* webpackChunkName: "dashboard" */
'@/pages/Dashboard/Dashboard.vue'),
children: [
{
name: 'dashboard.home',
path: '/home',
component: () => import(/* webpackChunkName: "dashboard_home" */
'@/pages/Dashboard/Home.vue'),
},
],
},
];
export default routes;

21
client/src/store/index.js Normal file
View File

@@ -0,0 +1,21 @@
import Vue from 'vue';
import Vuex from 'vuex';
import vuexI18n from 'vuex-i18n';
import sidebar from '@/store/modules/sidebar';
import app from '@/store/modules/app';
const debug = process.env.NODE_ENV !== 'production';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
app,
sidebar,
},
strict: debug,
});
Vue.use(vuexI18n.plugin, store);
export default store;

View File

@@ -0,0 +1,26 @@
/* eslint-disable no-param-reassign */
const state = {
appName: 'Ratteb',
appVersion: '1.0.0',
appBuild: '1000',
pageTitle: 'Welcome',
actions: [],
};
const getters = {
getAppName: s => s.appName,
getAppVersion: s => s.appVersion,
getAppBuild: s => s.appBuild,
getPageTitle: s => s.pageTitle,
};
const actions = {
setPageTitle(s, title) {
s.title = title;
},
};
export default { state, actions, getters };

View File

@@ -0,0 +1,58 @@
import ApiService from '~/plugins/api-service';
let state = {
token: localStorage.getItem('token') || '',
errors: {},
role: {}
};
const getters = {
authCheck: s => s.token,
authToken: s => s.token,
authorizedUserRole: s => s.role,
};
const actions = {
/**
* User login authentication request.
*/
async authRequest({ commit }, { form }) {
const response = await ApiService.post('auth/login', form);
const { data } = response;
if (data.token) {
commit('setToken', data.token);
}
return data;
},
/**
* Send reset password email or SMS.
*/
sendResetPassword({}, { email }) {
return ApiService.post('auth/send_reset_password', { email });
},
/**
* Verify reset password verification code.
*/
verifyResetPasswordToken({ commit, dispatch }, { token }) {
return ApiService.post(`reset/${token}`);
}
};
const mutations = {
setToken(state, token) {
localStorage.setItem('token', token);
state.token = token;
},
removeToken(state) {
localStorage.removeItem('token');
state.token = '';
},
};
export default {state, actions, mutations, getters};

View File

@@ -0,0 +1,48 @@
const state = {
items: [
{
name: 'Home',
to: 'dashboard.home',
icon: 'home',
count: null,
},
{
name: 'Products',
to: 'dashboard.home',
},
{
name: 'Customers',
to: 'dashboard.home',
},
{
name: 'Suppliers',
to: 'dashboard.home',
},
{
name: 'Reports',
to: 'dashboard.home',
},
],
};
const getters = {
getSidebarItems: s => s.items,
getSidebarItem: s => name => s.items.find(item => item.name === name),
};
const actions = {
/**
* Set count to the given sidebar item.
*/
setSidebarItemCount(s, { name, count }) {
s.items.forEach((item) => {
if (item.name === name) {
item.count = count;
}
})
}
}
export default { state, getters, actions };

2
client/src/utils/vuex.js Normal file
View File

@@ -0,0 +1,2 @@
export const set = property => (store, payload) => (store[property] = payload)
export const toggle = property => store => (store[property] = !store[property])

View File

@@ -0,0 +1,35 @@
<template>
<div class="sidebar__item" :class="computedClasses">
<router-link :to="to">
<span class="title">{{ name }}</span>
<span v-if="count" class="count">{{ count }}</span>
</router-link>
<div v-if="hasChildren" class="sidebar__item-children">
</div>
</div>
</template>
<script>
export default {
name: 'sidebar-item',
props: {
name: String,
to: String,
icon: String,
children: Array,
},
computed: {
computedClasses() {
return {
[`sidebar__item--${this.name}`]: this.name,
};
},
},
methods: {
hasChildren() {
return this.children.length > 0;
},
},
};
</script>

View File

@@ -0,0 +1,54 @@
<template>
<div class="dashboard__sidebar sidebar">
<div class="sidebar__app">
<div class="sidebar__app-logo">
</div>
<div class="sidebar__app-info">
<h6 class="title">{{ appName }}</h6>
<span class="version">{{ appVersion }} (build {{ appBuild }})</span>
</div>
</div>
<div class="sidebar__menu">
<SidebarItem
v-for="(item, index) in sidebarItems" :key="index"
:to="item.to"
:name="item.name"
:count="item.count"
:children="item.children"
/>
</div>
</div>
</template>
<script>
import SidebarItem from '@/views/Sidebar/SidebarItem';
import { mapGetters } from 'vuex';
export default {
name: 'sidebar',
components: {
SidebarItem,
},
computed: {
...mapGetters({
appName: 'getAppName',
appVersion: 'getAppVersion',
appBuild: 'getAppBuild',
sidebarItems: 'getSidebarItems',
}),
},
};
</script>
<style>
.dashboard__sidebar{
width: 210px;
background: red;
transition: width 0.3s;
height: 100%;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="topbar">
<div class="topbar__title">
<h3>{{ pageTitle }}</h3>
</div>
<div class="topbar__actions">
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'dashboard-topbar',
computed: {
...mapGetters({
pageTitle: 'getPageTitle',
}),
},
};
</script>
<style lang="scss">
.topbar{
padding-bottom: 12px;
&__title{
h3{
font-size: 30px;
}
}
&__actions{
}
}
</style>