Subscription Plan 70%
This commit is contained in:
parent
56fa925770
commit
2fee1d2ae1
47
README.md
47
README.md
|
|
@ -1,14 +1,53 @@
|
|||
# Documentation
|
||||
|
||||
## Loading
|
||||
You can use the global loader (defined in _src/store/app_) using the 'app/TOGGLE_OVERLAY' mutation.
|
||||
You can use the global loader (defined in _"src/store/app"_) using the 'app/TOGGLE_OVERLAY' mutation.
|
||||
```javascript
|
||||
this.$store.commit('app/TOGGLE_OVERLAY')
|
||||
```
|
||||
It displays a loader only on the content of the page and does not include the main menu.
|
||||
|
||||
## Validations
|
||||
Validations are in the file _"src/@core/utils/validations/validations.js"_.\
|
||||
|
||||
## UI Elements
|
||||
--------------
|
||||
|
||||
## The Navbar
|
||||
Navbar is the horizontal menu displayed at the top of the application.
|
||||
It is located at _src/@core/layouts/components/app-navbar/AppNavbarVerticalLayout.vue_.
|
||||
It is located at _"src/@core/layouts/components/app-navbar/AppNavbarVerticalLayout.vue"_.
|
||||
|
||||
## Main Menu
|
||||
The giant menu displayed to the left of the screen.\
|
||||
The list of menus to display is located at _src/navigation/vertical_
|
||||
Main menu is the giant menu displayed to the left of the screen.\
|
||||
The list of menus to display is located at _"src/navigation/vertical"_.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Install & Run
|
||||
|
||||
Get the project by cloning it to a folder (or just download the zip).
|
||||
```bash
|
||||
git clone <todo: add the git address>
|
||||
cd <todo: add branch name>
|
||||
```
|
||||
|
||||
Then, install all the node modules. The project is compatible with node v14;
|
||||
using higher node versions might cause you installation errors.
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
or
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
To run the project, use the 'serve' command.
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
or
|
||||
```bash
|
||||
yarn serve
|
||||
```
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</b-avatar>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item
|
||||
<!-- <b-dropdown-item
|
||||
:to="{ name: 'pages-profile'}"
|
||||
link-class="d-flex align-items-center"
|
||||
>
|
||||
|
|
@ -33,8 +33,9 @@
|
|||
class="mr-50"
|
||||
/>
|
||||
<span>Profile</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
</b-dropdown-item> -->
|
||||
|
||||
<!-- <b-dropdown-item
|
||||
:to="{ name: 'apps-email' }"
|
||||
link-class="d-flex align-items-center"
|
||||
>
|
||||
|
|
@ -44,8 +45,9 @@
|
|||
class="mr-50"
|
||||
/>
|
||||
<span>Inbox</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
</b-dropdown-item> -->
|
||||
|
||||
<!-- <b-dropdown-item
|
||||
:to="{ name: 'apps-todo' }"
|
||||
link-class="d-flex align-items-center"
|
||||
>
|
||||
|
|
@ -55,8 +57,9 @@
|
|||
class="mr-50"
|
||||
/>
|
||||
<span>Task</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
</b-dropdown-item> -->
|
||||
|
||||
<!-- <b-dropdown-item
|
||||
:to="{ name: 'apps-chat' }"
|
||||
link-class="d-flex align-items-center"
|
||||
>
|
||||
|
|
@ -66,11 +69,11 @@
|
|||
class="mr-50"
|
||||
/>
|
||||
<span>Chat</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown-item> -->
|
||||
|
||||
<b-dropdown-divider />
|
||||
|
||||
<b-dropdown-item
|
||||
<!-- <b-dropdown-item
|
||||
:to="{ name: 'pages-account-setting' }"
|
||||
link-class="d-flex align-items-center"
|
||||
>
|
||||
|
|
@ -80,8 +83,9 @@
|
|||
class="mr-50"
|
||||
/>
|
||||
<span>Settings</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
</b-dropdown-item> -->
|
||||
|
||||
<!-- <b-dropdown-item
|
||||
:to="{ name: 'pages-pricing' }"
|
||||
link-class="d-flex align-items-center"
|
||||
>
|
||||
|
|
@ -91,15 +95,17 @@
|
|||
class="mr-50"
|
||||
/>
|
||||
<span>Pricing</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item :to="{ name: 'pages-faq' }" link-class="d-flex align-items-center">
|
||||
</b-dropdown-item> -->
|
||||
|
||||
<!-- <b-dropdown-item :to="{ name: 'pages-faq' }" link-class="d-flex align-items-center">
|
||||
<feather-icon
|
||||
size="16"
|
||||
icon="HelpCircleIcon"
|
||||
class="mr-50"
|
||||
/>
|
||||
<span>FAQ</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown-item> -->
|
||||
|
||||
<b-dropdown-item link-class="d-flex align-items-center" v-on:click="logout" >
|
||||
<feather-icon size="16" icon="LogOutIcon" class="mr-50" />
|
||||
<span>Logout</span>
|
||||
|
|
|
|||
|
|
@ -1,99 +1,107 @@
|
|||
import { extend, localize } from 'vee-validate'
|
||||
import {
|
||||
required as rule_required,
|
||||
email as rule_email,
|
||||
min as rule_min,
|
||||
confirmed as rule_confirmed,
|
||||
regex as rule_regex,
|
||||
between as rule_between,
|
||||
alpha as rule_alpha,
|
||||
integer as rule_integer,
|
||||
digits as rule_digits,
|
||||
alpha_dash as rule_alpha_dash,
|
||||
alpha_num as rule_alpha_num,
|
||||
length as rule_length,
|
||||
alpha as rule_alpha,
|
||||
alpha_dash as rule_alpha_dash,
|
||||
alpha_num as rule_alpha_num,
|
||||
between as rule_between,
|
||||
confirmed as rule_confirmed,
|
||||
digits as rule_digits,
|
||||
email as rule_email,
|
||||
integer as rule_integer,
|
||||
length as rule_length,
|
||||
min as rule_min,
|
||||
numeric as rule_numeric,
|
||||
regex as rule_regex,
|
||||
required as rule_required,
|
||||
} from 'vee-validate/dist/rules'
|
||||
import ar from 'vee-validate/dist/locale/ar.json'
|
||||
import en from 'vee-validate/dist/locale/en.json'
|
||||
import enBuiltin from 'vee-validate/dist/locale/en.json'
|
||||
import enCustom from '@/libs/i18n/locales/en.json'
|
||||
|
||||
// eslint-disable-next-line object-curly-newline
|
||||
import { validatorPositive, validatorUrlValidator, validatorPassword, validatorCreditCard } from './validators'
|
||||
|
||||
// ////////////////////////////////////////////////////////
|
||||
// General
|
||||
// ////////////////////////////////////////////////////////
|
||||
|
||||
export const required = extend('required', rule_required)
|
||||
|
||||
export const email = extend('email', rule_email)
|
||||
|
||||
export const min = extend('min', rule_min)
|
||||
|
||||
export const confirmed = extend('confirmed', rule_confirmed)
|
||||
|
||||
export const regex = extend('regex', rule_regex)
|
||||
|
||||
export const between = extend('between', rule_between)
|
||||
//==============================================================================
|
||||
// Built-in Rules
|
||||
//==============================================================================
|
||||
|
||||
export const alpha = extend('alpha', rule_alpha)
|
||||
|
||||
export const integer = extend('integer', rule_integer)
|
||||
|
||||
export const digits = extend('digits', rule_digits)
|
||||
|
||||
export const alphaDash = extend('alpha-dash', rule_alpha_dash)
|
||||
|
||||
export const alphaNum = extend('alpha-num', rule_alpha_num)
|
||||
|
||||
export const between = extend('between', rule_between)
|
||||
|
||||
export const confirmed = extend('confirmed', rule_confirmed)
|
||||
|
||||
export const digits = extend('digits', rule_digits)
|
||||
|
||||
export const email = extend('email', rule_email)
|
||||
|
||||
export const integer = extend('integer', rule_integer)
|
||||
|
||||
export const length = extend('length', rule_length)
|
||||
|
||||
export const min = extend('min', rule_min)
|
||||
|
||||
export const numeric = extend('numeric', rule_numeric)
|
||||
|
||||
export const regex = extend('regex', rule_regex)
|
||||
|
||||
export const required = extend('required', rule_required)
|
||||
|
||||
//==============================================================================
|
||||
// Custom Rules
|
||||
//==============================================================================
|
||||
|
||||
export const positive = extend('positive', {
|
||||
validate: validatorPositive,
|
||||
message: 'Please enter positive number!',
|
||||
validate: validatorPositive,
|
||||
message: 'Please enter positive number!',
|
||||
})
|
||||
|
||||
export const credit = extend('credit-card', {
|
||||
validate: validatorCreditCard,
|
||||
message: 'It is not valid credit card!',
|
||||
validate: validatorCreditCard,
|
||||
message: 'It is not valid credit card!',
|
||||
})
|
||||
|
||||
export const password = extend('password', {
|
||||
validate: validatorPassword,
|
||||
message: 'Your {_field_} must contain at least one uppercase, one lowercase, one special character and one digit',
|
||||
validate: validatorPassword,
|
||||
message: 'Your {_field_} must contain at least one uppercase, one lowercase, one special character and one digit',
|
||||
})
|
||||
|
||||
export const url = extend('url', {
|
||||
validate: validatorUrlValidator,
|
||||
message: 'URL is invalid',
|
||||
validate: validatorUrlValidator,
|
||||
message: 'URL is invalid',
|
||||
})
|
||||
|
||||
// Install English and Arabic localizations.
|
||||
//==============================================================================
|
||||
// Localization
|
||||
//==============================================================================
|
||||
localize({
|
||||
en: {
|
||||
messages: en.messages,
|
||||
names: {
|
||||
email: 'Email',
|
||||
password: 'Password',
|
||||
},
|
||||
fields: {
|
||||
password: {
|
||||
min: '{_field_} is too short, you want to get hacked?',
|
||||
},
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
messages: ar.messages,
|
||||
names: {
|
||||
email: 'البريد الإلكتروني',
|
||||
password: 'كلمة السر',
|
||||
},
|
||||
fields: {
|
||||
password: {
|
||||
min: 'كلمة السر قصيرة جداً سيتم اختراقك',
|
||||
},
|
||||
},
|
||||
},
|
||||
en: {
|
||||
messages: enBuiltin.messages,
|
||||
names: enCustom.fields,
|
||||
fields: {
|
||||
password: {
|
||||
min: '{_field_} is too short, you want to get hacked?',
|
||||
},
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
messages: ar.messages,
|
||||
names: {
|
||||
email: 'البريد الإلكتروني',
|
||||
password: 'كلمة السر',
|
||||
},
|
||||
fields: {
|
||||
password: {
|
||||
min: 'كلمة السر قصيرة جداً سيتم اختراقك',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// ////////////////////////////////////////////////////////
|
||||
// NOTE:
|
||||
// Quasar validation for reference only
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import router from '@/router'
|
|||
import ability from '@/libs/acl/ability'
|
||||
|
||||
// Constants
|
||||
const BEARER_TOKEN_KEY = 'adminToken'
|
||||
const USER_DATA_KEY = 'adminData'
|
||||
const TOKEN_KEY = 'adminToken'
|
||||
const USER_DATA_KEY = 'adminData'
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -14,7 +14,7 @@ const USER_DATA_KEY = 'adminData'
|
|||
* please update this function.
|
||||
*/
|
||||
export const isUserLoggedIn = () => {
|
||||
return localStorage.getItem(BEARER_TOKEN_KEY) !== null
|
||||
return localStorage.getItem(TOKEN_KEY) !== null
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -24,13 +24,15 @@ export const isUserLoggedIn = () => {
|
|||
* thus, whether a token is available or not. This must be checked by whoever
|
||||
* calls this function, using the above isUserLoggedIn() function.
|
||||
*/
|
||||
export const getAccessToken = () => localStorage.getItem(BEARER_TOKEN_KEY)
|
||||
export const getAccessToken = () => localStorage.getItem(TOKEN_KEY)
|
||||
|
||||
|
||||
/**
|
||||
* Sets the access token.
|
||||
*/
|
||||
export const setAccessToken = (token) => localStorage.setItem(BEARER_TOKEN_KEY, token)
|
||||
export const setAccessToken = (token) => {
|
||||
localStorage.setItem(TOKEN_KEY, token)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -42,7 +44,7 @@ export const getUserData = () => {
|
|||
|
||||
|
||||
/**
|
||||
* Sets the user's data.
|
||||
* Stores the user's data.
|
||||
*/
|
||||
export const setUserData = (userData) => {
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData))
|
||||
|
|
@ -51,15 +53,16 @@ export const setUserData = (userData) => {
|
|||
|
||||
/**
|
||||
* Logs the user with the provided data into the appliaction.
|
||||
* It does three things:
|
||||
* - stores the user's data in the local storage
|
||||
* It does four things:
|
||||
* - stores the token in local storage
|
||||
* - stores the user's data in local storage
|
||||
* - sets the user's permissions
|
||||
* - redirects the user to the home page
|
||||
*
|
||||
* Might be updated later on to use cookies instead of local storage.
|
||||
*/
|
||||
export const login = (userData) => {
|
||||
// Store the user's access token
|
||||
export const login = (userData, token) => {
|
||||
localStorage.setItem(TOKEN_KEY, token)
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData))
|
||||
|
||||
// Set the user's permissions
|
||||
|
|
@ -67,10 +70,10 @@ export const login = (userData) => {
|
|||
// use the actual user permissions.
|
||||
ability.update(fullAbility)
|
||||
|
||||
// Redirect to home page
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logs the user out of the application.
|
||||
* It does undo everything that was done in the login() function.
|
||||
|
|
@ -80,17 +83,17 @@ export const logout = () => {
|
|||
// ? If you like, you can also make API call to backend to blacklist used token
|
||||
// localStorage.removeItem(useJwt.jwtConfig.storageTokenKeyName)
|
||||
// localStorage.removeItem(useJwt.jwtConfig.storageRefreshTokenKeyName)
|
||||
localStorage.removeItem(BEARER_TOKEN_KEY)
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
localStorage.removeItem(USER_DATA_KEY)
|
||||
|
||||
// Reset ability (user permissions)
|
||||
ability.update(initialAbility)
|
||||
|
||||
// Redirect to login page
|
||||
router.push({ name: 'login' })
|
||||
// router.push({ name: 'auth-login' })
|
||||
// router.push({ path: '/login' })
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function is used for demo purpose route navigation
|
||||
* In real app you won't need this function because your app will navigate to same route for each users regardless of ability
|
||||
|
|
|
|||
11
src/axios.js
11
src/axios.js
|
|
@ -1,8 +1,7 @@
|
|||
import axios from 'axios'
|
||||
import router from '@/router'
|
||||
import { getAccessToken } from '@/auth/utils'
|
||||
import { getAccessToken, logout } from '@/auth/utils'
|
||||
|
||||
const baseURL = 'https://admin.ira-lex.com/api/v1/'
|
||||
export const baseURL = 'https://admin.ira-lex.com/api/v1/'
|
||||
|
||||
const ConfiguredAxios = axios.create({
|
||||
baseURL,
|
||||
|
|
@ -12,7 +11,7 @@ const ConfiguredAxios = axios.create({
|
|||
'Access-Control-Allow-Header': 'Content-Type',
|
||||
'Accept': "application/json",
|
||||
'Content-Type' : 'application/json',
|
||||
// 'Authorization': getAccessToken() ?? '',
|
||||
'Authorization': getAccessToken() ?? '',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma':'Pragma',
|
||||
'Expires': '0',
|
||||
|
|
@ -23,7 +22,7 @@ const ConfiguredAxios = axios.create({
|
|||
ConfiguredAxios.interceptors.request.use(
|
||||
// Do something before the request is sent.
|
||||
config => {
|
||||
// config.headers["Authorization"] = getAccessToken() ?? ''
|
||||
config.headers["Authorization"] = getAccessToken() ?? ''
|
||||
config.headers["Access-Control-Allow-Origin"] = '*'
|
||||
return config
|
||||
},
|
||||
|
|
@ -43,7 +42,7 @@ ConfiguredAxios.interceptors.response.use(
|
|||
function ({response}) {
|
||||
if (response.status === 401)
|
||||
{
|
||||
router.push({ path: '/login' })
|
||||
logout()
|
||||
}
|
||||
return Promise.reject(response)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<!--
|
||||
CHECKBOX
|
||||
|
||||
DESCRIPTION:
|
||||
A TWO STATE (BOOLEAN) INPUT WITH AN ATTACHED LABEL.
|
||||
|
||||
PROPS:
|
||||
-------
|
||||
| Property Name | Type | Default Value | Description |
|
||||
|-----------------|---------|---------------|--------------------------------------------------------------------------|
|
||||
| value (v-model) | Boolean | '' | the value of the checkbox, whether it is checked or not |
|
||||
| label | String | '' | the text to put above the checkbox |
|
||||
| labelClass | String | '' | a custom class to use for the label |
|
||||
| description | String | '' | the text to put below the checkbox |
|
||||
| name | String | '' | the name of the <input> as well as used by vee-validate for localization |
|
||||
|
||||
EVENTS:
|
||||
--------
|
||||
| Event Name | Description |
|
||||
|------------|--------------------------------|
|
||||
| input | invoked when the input changes |
|
||||
|
||||
-->
|
||||
|
||||
<template>
|
||||
<b-form-group
|
||||
v-bind:label="label"
|
||||
v-bind:label-for="name"
|
||||
v-bind:label-class="labelClass"
|
||||
v-bind:description="description"
|
||||
>
|
||||
<b-form-checkbox
|
||||
v-bind:name="name"
|
||||
v-model="MutableValue_D"
|
||||
v-on:input="$emit('input', MutableValue_D)"
|
||||
/>
|
||||
<small style="white-space: pre;"> </small>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BFormGroup, BFormCheckbox } from 'bootstrap-vue'
|
||||
|
||||
export default {
|
||||
name: 'CheckboxComponent',
|
||||
|
||||
props: {
|
||||
// NOTE(pooya): `name` is checked by vee-validate so make sure it's
|
||||
// provided inside @/libs/i18n/vee-validate-locales in the fields
|
||||
// property.
|
||||
|
||||
value: { type: Boolean , required: false , default: false },
|
||||
label: { type: String , required: false , default: '' },
|
||||
labelClass: { type: String , required: false , default: '' },
|
||||
description: { type: String , required: false , default: '' },
|
||||
name: { type: String , required: false , default: '' },
|
||||
},
|
||||
|
||||
components: {
|
||||
BFormGroup,
|
||||
BFormCheckbox,
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
MutableValue_D: this.value,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(NewValue, OldValue)
|
||||
{
|
||||
this.MutableValue_D = NewValue
|
||||
|
||||
// ❓(pooya): Should I emit an input event when the v-model
|
||||
// is changed from the parent component or not?
|
||||
// It logically doesn't make sense but will it lead to bugs?
|
||||
//
|
||||
// this.$emit('input', NewValue)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<!--
|
||||
CONFIRM MODAL
|
||||
|
||||
DESCRIPTION:
|
||||
A MODAL THAT ALLOWS THE USER TO CONFIRM OR CANCEL AN OPERATION.
|
||||
|
||||
HOW TO USE:
|
||||
First, import the component, put it in the template section, and then use
|
||||
VBModal bootstrap-vue component and pass it modal-error argument to open the
|
||||
modal.
|
||||
|
||||
EXAMPLE:
|
||||
<b-button v-b-modal:modal-error>Add New User</b-button>
|
||||
<ConfirmModal />
|
||||
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ConfirmModalComponent'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<!--
|
||||
DATEPICKER
|
||||
|
||||
DESCRIPTION:
|
||||
A DATE PICKER, WITH ATTACHED VALIDATION AND LABEL.
|
||||
|
||||
PROPS:
|
||||
-------
|
||||
| Property Name | Type | Default Value | Description |
|
||||
|-----------------|---------|---------------|--------------------------------------------------------------------------|
|
||||
| value (v-model) | String | '' | the selected date |
|
||||
| rules | String | 'required' | validation rules like min:3, numeric, etc |
|
||||
| notRequired | Boolean | false | whether the required validation should be applied or not |
|
||||
| noValidation | Boolean | false | whether to disable validation or not |
|
||||
| label | String | '' | the text to put above the datepicker |
|
||||
| labelClass | String | '' | a custom class to use for the label |
|
||||
| description | String | '' | the text to put below the datepicker |
|
||||
| name | String | '' | the name of the <input> as well as used by vee-validate for localization |
|
||||
| readonly | Boolean | false | whether the datepicker is editable or not |
|
||||
|
||||
EVENTS:
|
||||
--------
|
||||
| Event Name | Description |
|
||||
|------------|--------------------------------|
|
||||
| input | invoked when the input changes |
|
||||
|
||||
-->
|
||||
|
||||
<template>
|
||||
<ValidationProvider v-bind:name="name" v-bind:rules="Rules" v-slot="{ errors }">
|
||||
<b-form-group
|
||||
v-bind:label="label"
|
||||
v-bind:label-for="name"
|
||||
v-bind:label-class="labelClass"
|
||||
v-bind:description="description"
|
||||
>
|
||||
<b-form-datepicker
|
||||
v-bind:class="{ 'is-invalid': errors.length > 0 }"
|
||||
v-bind:name="name"
|
||||
v-model="MutableValue_D"
|
||||
v-on:input="$emit('input', MutableValue_D)"
|
||||
v-bind:readonly="readonly"
|
||||
/>
|
||||
<small class="text-danger" style="white-space: pre;"> {{ errors[0] }}</small>
|
||||
</b-form-group>
|
||||
</ValidationProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BFormGroup, BFormDatepicker } from 'bootstrap-vue'
|
||||
import { required } from '@validations'
|
||||
|
||||
export default {
|
||||
name: 'DatepickerComponent',
|
||||
|
||||
props: {
|
||||
// NOTE(pooya): `name` is checked by vee-validate so make sure it's
|
||||
// provided inside @/libs/i18n/vee-validate-locales in the fields
|
||||
// property.
|
||||
|
||||
value: { type: String , required: false , default: '' },
|
||||
rules: { type: String , required: false , default: '' },
|
||||
notRequired: { type: Boolean , required: false , default: false },
|
||||
noValidation: { type: Boolean , required: false , default: false },
|
||||
label: { type: String , required: false , default: '' },
|
||||
labelClass: { type: String , required: false , default: '' },
|
||||
description: { type: String , required: false , default: '' },
|
||||
name: { type: String , required: false , default: '' },
|
||||
readonly: { type: Boolean , required: false , default: false },
|
||||
},
|
||||
|
||||
components: {
|
||||
BFormGroup,
|
||||
BFormDatepicker,
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
MutableValue_D: this.value,
|
||||
|
||||
// validations rules
|
||||
required,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(NewValue, OldValue)
|
||||
{
|
||||
this.MutableValue_D = NewValue
|
||||
|
||||
// ❓(pooya): Should I emit an input event when the v-model
|
||||
// is changed from the parent component or not?
|
||||
// It logically doesn't make sense but will it lead to bugs?
|
||||
//
|
||||
// this.$emit('input', NewValue)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
Rules()
|
||||
{
|
||||
if( this.noValidation )
|
||||
return ''
|
||||
|
||||
if( this.notRequired )
|
||||
return (this.rules.trim()) ? `${this.rules}` : ''
|
||||
else
|
||||
return (this.rules.trim()) ? `required|${this.rules}` : 'required'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -16,7 +16,7 @@ https://projects.lukehaas.me/css-loaders/
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FullPageLoadingComponent',
|
||||
name: 'FullpageLoadingComponent',
|
||||
|
||||
props: {
|
||||
// Whether to display the component or not
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
<!--
|
||||
PASSWORD TEXTBOX
|
||||
|
||||
PROPS:
|
||||
-------
|
||||
| Property Name | Type | Default Value | Description |
|
||||
|--------------------|---------|--------------------|--------------------------------------------------------------------------|
|
||||
| value (v-model) | String | '' | the text of the textbox |
|
||||
| rules | String | 'required|min:8' | validation rules like max:3, numeric, etc |
|
||||
| noValidation | Boolean | false | whether to disable validation or not |
|
||||
| label | String | '' | the text to put above the textbox |
|
||||
| labelClass | String | '' | a custom class to use for the label |
|
||||
| description | String | '' | the text to put below the textbox |
|
||||
| name | String | '' | the name of the <input> as well as used by vee-validate for localization |
|
||||
| placeholder | String | '' | the placeholder text of the textbox |
|
||||
| forgotPassword | Boolean | false | whether to display a forgot password link in front of the label or not |
|
||||
| forgotPasswordText | String | 'Forgot Password?' | the text to display in the forgot password link |
|
||||
|
||||
EVENTS:
|
||||
--------
|
||||
| Event Name | Description |
|
||||
|----------------|---------------------------------------|
|
||||
| input | when the value is changed |
|
||||
| forgotPassword | when the 'Forgot Password' is clicked |
|
||||
|
||||
-->
|
||||
|
||||
<template>
|
||||
<ValidationObserver ref="passwordTextbox" v-slot="{}">
|
||||
<ValidationProvider
|
||||
v-bind:name="name"
|
||||
v-bind:rules="Rules"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<b-form-group>
|
||||
<div class="d-flex justify-content-between">
|
||||
<label v-bind:for="name" v-bind:class="labelClass">{{ label }}</label>
|
||||
<b-link v-if="forgotPassword" v-on:click="$emit('forgotPassword')">
|
||||
<small class="text-muted">{{ forgotPasswordText }}</small>
|
||||
</b-link>
|
||||
</div>
|
||||
<div>
|
||||
<b-input-group
|
||||
class="input-group-merge"
|
||||
v-bind:class="errors.length > 0 ? 'is-invalid' : null"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="MutableValue_D"
|
||||
v-bind:state="errors.length > 0 ? false : null"
|
||||
v-bind:type="passwordFieldType"
|
||||
v-bind:name="name"
|
||||
v-bind:placeholder="placeholder"
|
||||
v-on:input="$emit('input', MutableValue_D)"
|
||||
/>
|
||||
<b-input-group-append
|
||||
v-if="MutableValue_D.length > 0"
|
||||
class="cursor-pointer"
|
||||
is-text
|
||||
v-on:click="togglePasswordVisibility"
|
||||
>
|
||||
<feather-icon v-bind:icon="passwordToggleIcon" />
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
<small v-if="customErrorText" class="text-danger" style="white-space: pre;"> {{ customErrorText }}</small>
|
||||
<small v-else class="text-danger" style="white-space: pre;"> {{ errors[0] }}</small>
|
||||
</div>
|
||||
</b-form-group>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ValidationObserver, ValidationProvider } from 'vee-validate'
|
||||
import { BFormGroup, BFormInput, BInputGroup, BInputGroupAppend, BLink } from 'bootstrap-vue'
|
||||
import { togglePasswordVisibility } from '@core/mixins/ui/forms'
|
||||
|
||||
export default {
|
||||
name: 'ValidatedPasswordTextboxComponent',
|
||||
|
||||
props: {
|
||||
// NOTE(pooya): `name` is checked by vee-validate so make sure it's
|
||||
// provided inside @/libs/i18n/vee-validate-locales in the fields
|
||||
// property.
|
||||
|
||||
value: { required: false , default: '' },
|
||||
rules: { type: String , required: false , default: '' },
|
||||
noValidation: { type: Boolean , required: false , default: false },
|
||||
label: { type: String , required: false , default: '' },
|
||||
labelClass: { type: String , required: '' , default: '' },
|
||||
description: { type: String , required: false , default: '' },
|
||||
name: { type: String , required: false , default: '' },
|
||||
placeholder: { type: String , required: false , default: '' },
|
||||
forgotPassword: { type: Boolean , required: false , default: false },
|
||||
forgotPasswordText: { type: String , required: false , default: 'forgot password?' },
|
||||
|
||||
// quick hack, must be fixed!
|
||||
customErrorText: { type: String, required: false, default: undefined },
|
||||
},
|
||||
|
||||
components: {
|
||||
ValidationObserver,
|
||||
ValidationProvider,
|
||||
BFormGroup,
|
||||
BFormInput,
|
||||
BInputGroup,
|
||||
BInputGroupAppend,
|
||||
BLink,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
togglePasswordVisibility,
|
||||
],
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
MutableValue_D: '',
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(NewValue, OldValue)
|
||||
{
|
||||
this.MutableValue_D = NewValue
|
||||
this.$emit('input', NewValue)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
Rules()
|
||||
{
|
||||
if( this.noValidation === false )
|
||||
return (this.rules.trim()) ? `required|${this.rules}` : 'required|min:8'
|
||||
return ''
|
||||
},
|
||||
|
||||
passwordToggleIcon()
|
||||
{
|
||||
return this.passwordFieldType === 'password' ? 'EyeIcon' : 'EyeOffIcon'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
<!--
|
||||
TEXTBOX
|
||||
|
||||
DESCRIPTION:
|
||||
A SINGLE OR MULTI LINE TEXT INPUT, WITH ATTACHED VALIDATION AND LABEL.
|
||||
|
||||
PROPS:
|
||||
-------
|
||||
| Property Name | Type | Default Value | Description |
|
||||
|-----------------|---------|---------------|--------------------------------------------------------------------------|
|
||||
| value (v-model) | String | '' | the text of the textbox |
|
||||
| rules | String | 'required' | validation rules like min:3, numeric, etc |
|
||||
| notRequired | Boolean | false | whether the required validation should be applied or not |
|
||||
| noValidation | Boolean | false | whether to disable validation or not |
|
||||
| label | String | '' | the text to put above the textbox |
|
||||
| labelClass | String | '' | a custom class to use for the label |
|
||||
| description | String | '' | the text to put below the textbox |
|
||||
| name | String | '' | the name of the <input> as well as used by vee-validate for localization |
|
||||
| placeholder | String | '' | the placeholder text of the textbox |
|
||||
| multiline | Boolean | false | whether to use a <textarea> or just an <input> |
|
||||
| rows | Number | 4 | how long the multiline textbox should be |
|
||||
| readonly | Boolean | false | whether the textbox is editable or not |
|
||||
|
||||
EVENTS:
|
||||
--------
|
||||
| Event Name | Description |
|
||||
|------------|--------------------------------|
|
||||
| input | invoked when the input changes |
|
||||
|
||||
-->
|
||||
|
||||
<template>
|
||||
<ValidationProvider v-bind:name="name" v-bind:rules="Rules" v-slot="{ errors }">
|
||||
<b-form-group
|
||||
v-bind:label="label"
|
||||
v-bind:label-for="name"
|
||||
v-bind:label-class="labelClass"
|
||||
v-bind:description="description"
|
||||
>
|
||||
<b-form-textarea
|
||||
v-if="multiline"
|
||||
v-bind:class="{ 'is-invalid': errors.length > 0 }"
|
||||
v-bind:name="name"
|
||||
v-model="MutableValue_D"
|
||||
v-on:input="$emit('input', MutableValue_D)"
|
||||
v-bind:placeholder="placeholder"
|
||||
no-resize
|
||||
v-bind:rows="rows"
|
||||
v-bind:max-rows="rows"
|
||||
v-bind:readonly="readonly"
|
||||
/>
|
||||
<b-form-input
|
||||
v-else
|
||||
v-bind:class="{ 'is-invalid': errors.length > 0 }"
|
||||
v-bind:name="name"
|
||||
type="text"
|
||||
v-bind:placeholder="placeholder"
|
||||
v-model="MutableValue_D"
|
||||
v-on:input="$emit('input', MutableValue_D)"
|
||||
v-bind:readonly="readonly"
|
||||
/>
|
||||
<small class="text-danger" style="white-space: pre;"> {{ errors[0] }}</small>
|
||||
</b-form-group>
|
||||
</ValidationProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BFormGroup, BFormInput, BFormTextarea } from 'bootstrap-vue'
|
||||
import { required, email, numeric } from '@validations'
|
||||
|
||||
export default {
|
||||
name: 'TextboxComponent',
|
||||
|
||||
props: {
|
||||
// NOTE(pooya): `name` is checked by vee-validate so make sure it's
|
||||
// provided inside @/libs/i18n/vee-validate-locales in the fields
|
||||
// property.
|
||||
|
||||
value: { type: String , required: false , default: '' },
|
||||
rules: { type: String , required: false , default: '' },
|
||||
notRequired: { type: Boolean , required: false , default: false },
|
||||
noValidation: { type: Boolean , required: false , default: false },
|
||||
label: { type: String , required: false , default: '' },
|
||||
labelClass: { type: String , required: false , default: '' },
|
||||
description: { type: String , required: false , default: '' },
|
||||
name: { type: String , required: false , default: '' },
|
||||
placeholder: { type: String , required: false , default: '' },
|
||||
multiline: { type: Boolean , required: false , default: false },
|
||||
rows: { type: Number , required: false , default: 4 },
|
||||
readonly: { type: Boolean , required: false , default: false },
|
||||
},
|
||||
|
||||
components: {
|
||||
BFormGroup,
|
||||
BFormInput,
|
||||
BFormTextarea,
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
MutableValue_D: this.value,
|
||||
|
||||
// validations rules
|
||||
required,
|
||||
email,
|
||||
numeric,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(NewValue, OldValue)
|
||||
{
|
||||
this.MutableValue_D = NewValue
|
||||
|
||||
// ❓(pooya): Should I emit an input event when the v-model
|
||||
// is changed from the parent component or not?
|
||||
// It logically doesn't make sense but will it lead to bugs?
|
||||
//
|
||||
// this.$emit('input', NewValue)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
Rules()
|
||||
{
|
||||
if( this.noValidation )
|
||||
return ''
|
||||
|
||||
if( this.notRequired )
|
||||
return (this.rules.trim()) ? `${this.rules}` : ''
|
||||
else
|
||||
return (this.rules.trim()) ? `required|${this.rules}` : 'required'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -206,14 +206,14 @@ export default {
|
|||
is_pagenation: { type: Boolean, default: true },
|
||||
method: { type: String, default: "post" },
|
||||
search: { type: Boolean, default: true },
|
||||
options:{
|
||||
options: {
|
||||
type: Object,
|
||||
default: {
|
||||
default: () => ({
|
||||
placeholder:'Search',
|
||||
noContent: 'No data',
|
||||
is_row_selection: false,
|
||||
is_load_req: true
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
import Vue from 'vue'
|
||||
import FeatherIcon from '@core/components/feather-icon/FeatherIcon.vue'
|
||||
import { ValidationObserver, ValidationProvider } from 'vee-validate'
|
||||
import Textbox from '@/components/ui/Textbox.vue'
|
||||
import Checkbox from '@/components/ui/Checkbox.vue'
|
||||
import Datepicker from '@/components/ui/Datepicker.vue'
|
||||
import ConfirmModal from '@/components/ui/ConfirmModal.vue'
|
||||
import FullpageLoadingComponent from '@/components/ui/FullPageLoadingComponent.vue'
|
||||
|
||||
// Form Components
|
||||
Vue.component(Textbox.name, Textbox)
|
||||
Vue.component(Checkbox.name, Checkbox)
|
||||
Vue.component(Datepicker.name, Datepicker)
|
||||
|
||||
// Modals
|
||||
Vue.component(ConfirmModal.name, ConfirmModal)
|
||||
|
||||
// Validation
|
||||
Vue.component('ValidationObserver', ValidationObserver)
|
||||
Vue.component('ValidationProvider', ValidationProvider)
|
||||
|
||||
// Misc
|
||||
Vue.component(FeatherIcon.name, FeatherIcon)
|
||||
Vue.component(FullpageLoadingComponent.name, FullpageLoadingComponent)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
{
|
||||
"fields": {
|
||||
"title": "Title",
|
||||
"storage_limit": "Storage Limit",
|
||||
"description": "Description",
|
||||
"from_date": "Beginning Date",
|
||||
"to_date": "Ending Date",
|
||||
"is_active": "Active"
|
||||
},
|
||||
"message": {
|
||||
"title": "Card Title",
|
||||
"text": "Cake sesame snaps cupcake gingerbread danish I love gingerbread. Apple pie pie jujubes chupa chups muffin halvah lollipop. Chocolate cake oat cake tiramisu marzipan sugar plum. Donut sweet pie oat cake dragée fruitcake cotton candy lemon drops.",
|
||||
|
|
@ -16,7 +24,15 @@
|
|||
"action": "Action"
|
||||
}
|
||||
},
|
||||
|
||||
"Dashboard": "Dashboard",
|
||||
"Law Firms": "Law Firms",
|
||||
"Law Firms List": "Law Firms List",
|
||||
"Add Law Firm": "Add Law Firm",
|
||||
"Law Firm Reports": "Law Firm Reports",
|
||||
"Subscriptions": "Subscriptions",
|
||||
"Subscription Plans": "Subscription Plans",
|
||||
|
||||
"Users": "Users",
|
||||
"Users List": "Users List",
|
||||
"Add User": "Add User",
|
||||
|
|
|
|||
18
src/main.js
18
src/main.js
|
|
@ -11,7 +11,7 @@ import App from './App.vue'
|
|||
import './global-components'
|
||||
|
||||
// 3rd party plugins
|
||||
// import '@axios'
|
||||
// import '@axios' // ?
|
||||
import '@/libs/acl'
|
||||
import '@/libs/portal-vue'
|
||||
import '@/libs/clipboard'
|
||||
|
|
@ -23,33 +23,41 @@ import '@/libs/tour'
|
|||
// Axios Mock Adapter
|
||||
import '@/@fake-db/db'
|
||||
|
||||
|
||||
// Axios Configured
|
||||
import axios from '@/axios'
|
||||
Vue.prototype.$http = axios
|
||||
|
||||
|
||||
// BSV Plugin Registration
|
||||
Vue.use(ToastPlugin)
|
||||
Vue.use(ModalPlugin)
|
||||
|
||||
|
||||
// Composition API
|
||||
Vue.use(VueCompositionAPI)
|
||||
|
||||
|
||||
// Vue Tree Halower
|
||||
// import 'vue-tree-halower/dist/halower-tree.min.css'
|
||||
|
||||
|
||||
// Ant Design Vue
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/antd.css'
|
||||
Vue.use(Antd)
|
||||
|
||||
|
||||
// Vue Number Animation
|
||||
import VueNumber from 'vue-number-animation'
|
||||
Vue.use(VueNumber)
|
||||
|
||||
|
||||
// Feather font icon - For form-wizard
|
||||
// * Shall remove it if not using font-icons of feather-icons - For form-wizard
|
||||
require('@core/assets/fonts/feather/iconfont.css') // For form-wizard
|
||||
|
||||
|
||||
// import core styles
|
||||
require('@core/scss/core.scss')
|
||||
|
||||
|
|
@ -59,8 +67,8 @@ require('@/assets/scss/style.scss')
|
|||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export default [
|
||||
{
|
||||
title : 'Dashboard',
|
||||
icon : 'HomeIcon' ,
|
||||
route : 'dashboard',
|
||||
},
|
||||
// {
|
||||
// title : 'Dashboard',
|
||||
// icon : 'HomeIcon' ,
|
||||
// route : 'dashboard',
|
||||
// },
|
||||
{
|
||||
title: 'Users',
|
||||
icon: 'UsersIcon',
|
||||
|
|
@ -128,4 +128,16 @@ export default [
|
|||
// },
|
||||
// ],
|
||||
// },
|
||||
|
||||
// Subscriptions
|
||||
{
|
||||
title: 'Subscriptions',
|
||||
icon: 'FlagIcon',
|
||||
children: [
|
||||
{
|
||||
title: 'Subscription Plans',
|
||||
route: 'subscription-plans',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
@ -7,7 +7,7 @@ import { isUserLoggedIn, getUserData, getHomeRouteForLoggedInUser } from '@/auth
|
|||
// import apps from './routes/apps'
|
||||
// import dashboard from './routes/dashboard'
|
||||
// import uiElements from './routes/ui-elements/index'
|
||||
import pages from './routes/pages'
|
||||
import Pages from './routes/pages'
|
||||
// import chartsMaps from './routes/charts-maps'
|
||||
// import formsTable from './routes/forms-tables'
|
||||
// import others from './routes/others'
|
||||
|
|
@ -22,10 +22,10 @@ const router = new VueRouter({
|
|||
return { x: 0, y: 0 }
|
||||
},
|
||||
routes: [
|
||||
{ path: '/', redirect: { name: 'dashboard' } },
|
||||
{ path: '/', redirect: { name: 'users-list' } },
|
||||
// ...apps,
|
||||
// ...dashboard,
|
||||
...pages,
|
||||
...Pages,
|
||||
// ...chartsMaps,
|
||||
// ...formsTable,
|
||||
// ...uiElements,
|
||||
|
|
|
|||
|
|
@ -106,11 +106,11 @@ export default [
|
|||
// -----------------------------------------------------------------------
|
||||
// Misc Routes
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: () => import('@/views/Misc/Dashboard.vue'),
|
||||
},
|
||||
// {
|
||||
// path: '/dashboard',
|
||||
// name: 'dashboard',
|
||||
// component: () => import('@/views/Misc/Dashboard.vue'),
|
||||
// },
|
||||
{
|
||||
path: '/permissions',
|
||||
name: 'permissions',
|
||||
|
|
@ -131,4 +131,13 @@ export default [
|
|||
name: 'weblogs-add',
|
||||
component: () => import('@/views/Misc/AddWeblog.vue'),
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Subscriptions
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
path: '/subscriptions/plans',
|
||||
name: 'subscription-plans',
|
||||
component: () => import('@/views/Subscriptions/PlansList.vue'),
|
||||
},
|
||||
]
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="page--main d-flex justify-content-center align-items-center">
|
||||
<button v-on:click="TEST()">CLick Me!</button>
|
||||
<div id="add-user--container" class="d-flex flex-column" style="background: white; border: 1px solid #707070; border-radius: 5px;">
|
||||
<header class="d-flex justify-content-between align-items-center p-1">
|
||||
<h2 class="text-primary m-0">Add Law Firm</h2>
|
||||
|
|
@ -131,6 +132,20 @@ export default {
|
|||
],
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async TEST()
|
||||
{
|
||||
try
|
||||
{
|
||||
const { data } = await this.$http.get('admin/list/subscription-plan')
|
||||
console.log('data', data)
|
||||
}
|
||||
catch (e)
|
||||
{}
|
||||
console.log('hello')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
users: [1],
|
||||
|
||||
Model_D: null,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import XTbl, { Xtc } from '@/components/x-table/index'
|
||||
|
||||
const tbl = new XTbl('', 'UsersDeleted')
|
||||
const tbl = new XTbl('admin/list/subscription-plan', 'Law Firms')
|
||||
tbl.add(new Xtc('action', 'Action').noSort().renderSlot('action'))
|
||||
tbl.add(new Xtc('name', 'Name'))
|
||||
tbl.add(new Xtc('phone', 'Phone'))
|
||||
tbl.add(new Xtc('user_type', 'User Type'))
|
||||
tbl.add(new Xtc('email', 'Email'))
|
||||
tbl.add(new Xtc('status', 'Status'))
|
||||
tbl.add(new Xtc('employees', 'Employees'))
|
||||
// tbl.add(new Xtc('name', 'Name'))
|
||||
// tbl.add(new Xtc('phone', 'Phone'))
|
||||
// tbl.add(new Xtc('user_type', 'User Type'))
|
||||
// tbl.add(new Xtc('email', 'Email'))
|
||||
// tbl.add(new Xtc('status', 'Status'))
|
||||
// tbl.add(new Xtc('employees', 'Employees'))
|
||||
|
||||
export default tbl
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<div v-bind:class="{ 'page--main': Model_D === null }">
|
||||
<FullpageLoadingComponent v-bind:show="Loading_D" />
|
||||
|
||||
<!----------------------------- Title ----------------------------->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h3>Subscription Plans</h3>
|
||||
<div v-if="Model_D !== null" class="sx2">
|
||||
<b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button>
|
||||
<b-button
|
||||
variant="primary"
|
||||
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
|
||||
v-b-modal:modal-edit-plan
|
||||
>
|
||||
Add
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!------------------------------ Body ----------------------------->
|
||||
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
|
||||
|
||||
<!-- No Plans Exist -->
|
||||
<div id="no-plans--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
|
||||
<img src="@/assets/svg/hero.svg" alt="no-users-found">
|
||||
<h4 class="mt-2" style="text-align: center;">You haven't created any law firms.</h4>
|
||||
<p style="text-align: center;">Click the following button to create a new law firm.</p>
|
||||
<b-button
|
||||
variant="primary"
|
||||
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
|
||||
v-b-modal:modal-edit-plan
|
||||
>
|
||||
Add Subscription Plan
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
<!-- List of Users -->
|
||||
<div v-else>
|
||||
<XTable v-bind:model="Model_D">
|
||||
<template v-slot:storage_limit="{ record }">
|
||||
{{ record.storage_limit }} KB
|
||||
</template>
|
||||
<template v-slot:from_date="{ record }">
|
||||
{{ (new Date(record.from_date)).toLocaleDateString('en-US') }}
|
||||
</template>
|
||||
<template v-slot:to_date="{ record }">
|
||||
{{ (new Date(record.to_date)).toLocaleDateString('en-US') }}
|
||||
</template>
|
||||
<template v-slot:is_active="{ record }">
|
||||
<feather-icon v-if="record.is_active" icon="CheckIcon" size="22" />
|
||||
<feather-icon v-else icon="XIcon" size="22" />
|
||||
</template>
|
||||
<template v-slot:actions="{ record }">
|
||||
<span class="d-inline-flex" style="column-gap: 1rem;">
|
||||
<feather-icon
|
||||
icon="EditIcon"
|
||||
size="16"
|
||||
color="blue"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
<feather-icon
|
||||
icon="Trash2Icon"
|
||||
size="16"
|
||||
color="crimson"
|
||||
class="cursor-pointer"
|
||||
v-on:click="onDeleteRecord(record.id)"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</XTable>
|
||||
</div>
|
||||
</b-card>
|
||||
|
||||
<EditPlanModal v-on:submit="HandleNewPlanAdded" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BCard, BButton, VBModal } from 'bootstrap-vue'
|
||||
import Ripple from 'vue-ripple-directive'
|
||||
import EditPlanModal from './components/EditPlanModal'
|
||||
import * as TableCol from "./tables/plansTbl"
|
||||
import XTable from "@/components/x-table/XTable"
|
||||
|
||||
export default {
|
||||
name: 'UsersListView',
|
||||
|
||||
components: {
|
||||
BCard,
|
||||
BButton,
|
||||
EditPlanModal,
|
||||
XTable,
|
||||
},
|
||||
|
||||
directives: {
|
||||
'b-modal': VBModal,
|
||||
Ripple,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
Model_D: null,
|
||||
Loading_D: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async HandleNewPlanAdded(event)
|
||||
{
|
||||
// if create
|
||||
try
|
||||
{
|
||||
this.Loading_D = true
|
||||
// const { data } = this.$http.post('', this.Payload_D)
|
||||
}
|
||||
catch
|
||||
{}
|
||||
finally
|
||||
{
|
||||
this.Loading_D = false
|
||||
}
|
||||
|
||||
// if edit
|
||||
console.log('event is', event)
|
||||
},
|
||||
|
||||
async OnDeleteRecord(RecordId)
|
||||
{
|
||||
},
|
||||
},
|
||||
|
||||
created()
|
||||
{
|
||||
this.Model_D = TableCol.default
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#no-plans--container {
|
||||
position: absolute;
|
||||
bottom: -2.5rem;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
gap: 0.25rem;
|
||||
padding: 2rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<!--
|
||||
Documentation:
|
||||
|
||||
Description:
|
||||
A modal that prompts for a subscription plan.
|
||||
|
||||
How to use:
|
||||
Import the component, put it in the template section, then use VBModal
|
||||
bootstrap-vue component and pass it modal-edit-plan argument to open the modal.
|
||||
|
||||
Example:
|
||||
<b-button v-b-modal:modal-edit-plan>Add New Plan</b-button>
|
||||
<EditPlanModal v-on:submit="HandleNewPlanAdded" />
|
||||
|
||||
Events
|
||||
Name Parameters Description
|
||||
-------- ----------- ----------------------------------------------------------
|
||||
submit payload called when the submit button (the button in the footer) is clicked
|
||||
|
||||
-->
|
||||
|
||||
<template>
|
||||
<b-modal id="modal-edit-plan" v-bind:centered="true" size="lg" body-class="pb-0">
|
||||
<!-- Header -->
|
||||
<template v-slot:modal-header="{ close }">
|
||||
<h2 class="text-primary m-0">Add Plan</h2>
|
||||
<img class="cursor-pointer" src="@/assets/svg/cross.svg" alt="close-icon" v-on:click="close()">
|
||||
</template>
|
||||
|
||||
<!-- Body -->
|
||||
<template v-slot:default>
|
||||
<ValidationObserver ref="form" v-slot="{}">
|
||||
<b-form class="d-flex flex-column" v-on:submit.prevent>
|
||||
<b-row>
|
||||
<b-col md="6">
|
||||
<TextboxComponent
|
||||
v-model="Payload_D.title"
|
||||
label="Title"
|
||||
name="title"
|
||||
placeholder="Premium"
|
||||
/>
|
||||
</b-col>
|
||||
<b-col md="6">
|
||||
<TextboxComponent
|
||||
v-model="Payload_D.storage_limit"
|
||||
label="Storage Limit (KB)"
|
||||
name="storage_limit"
|
||||
placeholder="10"
|
||||
rules="numeric"
|
||||
/>
|
||||
</b-col>
|
||||
<b-col md="12">
|
||||
<TextboxComponent
|
||||
v-model="Payload_D.description"
|
||||
label="Description"
|
||||
name="description"
|
||||
placeholder="Suitable for large companies"
|
||||
multiline
|
||||
v-bind:rows="2"
|
||||
/>
|
||||
</b-col>
|
||||
<b-col md="6">
|
||||
<DatepickerComponent
|
||||
v-model="Payload_D.from_date"
|
||||
label="From"
|
||||
name="from_date"
|
||||
/>
|
||||
</b-col>
|
||||
<b-col md="6">
|
||||
<DatepickerComponent
|
||||
v-model="Payload_D.to_date"
|
||||
label="To"
|
||||
name="to_date"
|
||||
/>
|
||||
</b-col>
|
||||
<b-col md="12">
|
||||
<CheckboxComponent
|
||||
v-model="Payload_D.is_active"
|
||||
label="Is Active"
|
||||
name="is_active"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</ValidationObserver>
|
||||
</template>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-slot:modal-footer="{ hide }">
|
||||
<b-button
|
||||
class="mr-1"
|
||||
variant="primary"
|
||||
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
|
||||
v-on:click="OnSubmit(hide)"
|
||||
>
|
||||
Save Plan
|
||||
</b-button>
|
||||
or
|
||||
<a class="text-primary" v-on:click="hide()">Cancel</a>
|
||||
</template>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BForm, BButton, BRow, BCol, BFormGroup, BFormDatepicker } from 'bootstrap-vue'
|
||||
import Ripple from 'vue-ripple-directive'
|
||||
|
||||
export default {
|
||||
name: 'EditPlanModalComponent',
|
||||
|
||||
components: {
|
||||
BForm,
|
||||
BButton,
|
||||
BRow,
|
||||
BCol,
|
||||
BFormGroup,
|
||||
BFormDatepicker,
|
||||
},
|
||||
|
||||
directives: {
|
||||
Ripple,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
Payload_D: {
|
||||
title: '',
|
||||
storage_limit: '',
|
||||
description: '',
|
||||
from_date: '',
|
||||
to_date: '',
|
||||
is_active: true,
|
||||
},
|
||||
PlanOptions_D: [
|
||||
{ value: 0, title: 'Diamond' },
|
||||
{ value: 1, title: 'Gold' },
|
||||
{ value: 2, title: 'Silver' },
|
||||
{ value: 3, title: 'Bronze' },
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
OnSubmit(Hide)
|
||||
{
|
||||
this.$refs.form.validate().then(async success => {
|
||||
if (success)
|
||||
{
|
||||
this.$emit('submit', this.Payload_D)
|
||||
Hide()
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import XTbl, { Xtc } from '@/components/x-table/index'
|
||||
|
||||
const tbl = new XTbl('admin/list/subscription-plan', 'Law Firms')
|
||||
tbl.add(new Xtc('title', 'Title'))
|
||||
tbl.add(new Xtc('description', 'Description'))
|
||||
tbl.add(new Xtc('storage_limit', 'Storage Limit').noSort().renderSlot('storage_limit'))
|
||||
tbl.add(new Xtc('from_date', 'From').noSort().renderSlot('from_date'))
|
||||
tbl.add(new Xtc('to_date', 'To').noSort().renderSlot('to_date'))
|
||||
tbl.add(new Xtc('is_active', 'Is Active').noSort().renderSlot('is_active'))
|
||||
tbl.add(new Xtc('action', 'Actions').noSort().renderSlot('actions'))
|
||||
|
||||
export default tbl
|
||||
|
|
@ -178,7 +178,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable global-require */
|
||||
// NOTE: Do not use "@/axios" or this.$http as in here, we don't need a configurated
|
||||
// axios instance since has not logged in yet.
|
||||
import axios from 'axios'
|
||||
import { baseURL } from '@/axios'
|
||||
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate'
|
||||
import LogoComponent from '@core/layouts/components/Logo.vue'
|
||||
import { BRow, BCol, BLink, BFormGroup, BFormInput, BInputGroupAppend, BInputGroup, BFormCheckbox, BCardText, BCardTitle, BImg, BForm, BButton, BAlert, VBTooltip, BModal, VBModal } from 'bootstrap-vue'
|
||||
|
|
@ -302,12 +306,22 @@ export default {
|
|||
{
|
||||
this.Loading_D = true
|
||||
|
||||
const { data: tokenData } = await this.$http.post('auth/login', this.Payload_D)
|
||||
setAccessToken(tokenData.token)
|
||||
const { data: tokenData } = await axios({
|
||||
method: 'post',
|
||||
url: 'auth/login',
|
||||
data: this.Payload_D,
|
||||
baseURL,
|
||||
})
|
||||
const { data: userData } = await axios({
|
||||
method: 'get',
|
||||
url: 'admin/api/v1/auth/me',
|
||||
baseURL,
|
||||
headers: {
|
||||
'Authorization': tokenData.token,
|
||||
},
|
||||
})
|
||||
|
||||
const { data: userData } = await this.$http.get('admin/api/v1/auth/me', this.Payload_D)
|
||||
|
||||
login(userData)
|
||||
login(userData, tokenData.token)
|
||||
|
||||
this.$toast({
|
||||
component: ToastificationContent,
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ export default {
|
|||
BInputGroup,
|
||||
BInputGroupAppend,
|
||||
FullPageLoadingComponent,
|
||||
|
||||
// validations
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
|
|
|
|||
Loading…
Reference in New Issue