Subscription Plan 70%

This commit is contained in:
Ali Arya 2023-01-06 18:03:05 +03:30
parent 56fa925770
commit 2fee1d2ae1
26 changed files with 1129 additions and 139 deletions

View File

@ -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
```

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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;">&nbsp;</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>

View File

@ -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>

View File

@ -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;">&nbsp;{{ 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>

View File

@ -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

View File

@ -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;">&nbsp;{{ customErrorText }}</small>
<small v-else class="text-danger" style="white-space: pre;">&nbsp;{{ 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>

View File

@ -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;">&nbsp;{{ 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>

View File

@ -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
}
})
}
},

View File

@ -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)

View File

@ -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",

View File

@ -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')

View File

@ -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',
},
],
},
]

View File

@ -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,

View File

@ -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'),
},
]

View File

@ -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>

View File

@ -62,7 +62,7 @@ export default {
data() {
return {
users: [],
users: [1],
Model_D: null,
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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,

View File

@ -215,6 +215,7 @@ export default {
BInputGroup,
BInputGroupAppend,
FullPageLoadingComponent,
// validations
ValidationProvider,
ValidationObserver,