v6 update

This commit is contained in:
Mohit Panjwani
2022-01-10 16:06:17 +05:30
parent b770e6277f
commit bdea879273
722 changed files with 19047 additions and 9186 deletions

View File

@ -0,0 +1,230 @@
<template>
<BasePage>
<BasePageHeader :title="$t('modules.title')">
<BaseBreadcrumb>
<BaseBreadcrumbItem :title="$t('general.home')" to="dashboard" />
<BaseBreadcrumbItem :title="$tc('modules.module', 2)" to="#" active />
</BaseBreadcrumb>
</BasePageHeader>
<!-- Modules Section -->
<div v-if="hasApiToken && moduleStore.modules">
<BaseTabGroup class="-mb-5" @change="setStatusFilter">
<BaseTab :title="$t('general.all')" filter="" />
<BaseTab :title="$t('modules.installed')" filter="INSTALLED" />
</BaseTabGroup>
<!-- Modules Card Placeholder -->
<div
v-if="isFetchingModule"
class="
grid
mt-6
w-full
grid-cols-1
items-start
gap-6
lg:grid-cols-2
xl:grid-cols-3
"
>
<ModuleCardPlaceholder />
<ModuleCardPlaceholder />
<ModuleCardPlaceholder />
</div>
<!-- Modules Card -->
<div v-else>
<div
v-if="modules && modules.length"
class="
grid
mt-6
w-full
grid-cols-1
items-start
gap-6
lg:grid-cols-2
xl:grid-cols-3
"
>
<div v-for="(moduleData, idx) in modules" :key="idx">
<ModuleCard :data="moduleData" />
</div>
</div>
<div v-else class="mt-24">
<label class="flex items-center justify-center text-gray-500">
{{ $t('modules.no_modules_installed') }}
</label>
</div>
</div>
</div>
<BaseCard v-else class="mt-6">
<h6 class="text-gray-900 text-lg font-medium">
{{ $t('modules.connect_installation') }}
</h6>
<p class="mt-1 text-sm text-gray-500">
{{
$t('modules.api_token_description', {
url: globalStore.config.base_url.replace(/^http:\/\//, ''),
})
}}
</p>
<!-- Api Token Form -->
<div class="grid lg:grid-cols-2 mt-6">
<form action="" class="mt-6" @submit.prevent="submitApiToken">
<BaseInputGroup
:label="$t('modules.api_token')"
required
:error="v$.api_token.$error && v$.api_token.$errors[0].$message"
>
<BaseInput
v-model="moduleStore.currentUser.api_token"
:invalid="v$.api_token.$error"
@input="v$.api_token.$touch()"
/>
</BaseInputGroup>
<div class="flex space-x-2">
<BaseButton class="mt-6" :loading="isSaving" type="submit">
<template #left="slotProps">
<BaseIcon name="SaveIcon" :class="slotProps.class" />
</template>
{{ $t('general.save') }}
</BaseButton>
<a
:href="`${globalStore.config.base_url}/auth/customer/register`"
class="mt-6 block"
target="_blank"
>
<BaseButton variant="primary-outline" type="button">
Sign up & Get Token
</BaseButton>
</a>
</div>
</form>
</div>
</BaseCard>
</BasePage>
</template>
<script setup>
import { useModuleStore } from '@/scripts/admin/stores/module'
import { useGlobalStore } from '@/scripts/admin/stores/global'
import { computed, onMounted, reactive, ref, watchEffect } from 'vue'
import {
required,
minLength,
maxLength,
helpers,
requiredUnless,
} from '@vuelidate/validators'
import ModuleCard from './partials/ModuleCard.vue'
import ModuleCardPlaceholder from './partials/ModuleCardPlaceholder.vue'
import { useVuelidate } from '@vuelidate/core'
import { useI18n } from 'vue-i18n'
const moduleStore = useModuleStore()
const globalStore = useGlobalStore()
const activeTab = ref('')
const { t } = useI18n()
let isSaving = ref(false)
let isFetchingModule = ref(false)
const rules = computed(() => {
return {
api_token: {
required: helpers.withMessage(t('validation.required'), required),
minLength: helpers.withMessage(
t('validation.name_min_length', { count: 3 }),
minLength(3)
),
},
}
})
const hasApiToken = computed(() => {
if (moduleStore.apiToken) {
fetchModulesData()
return true
}
return false
})
const v$ = useVuelidate(
rules,
computed(() => moduleStore.currentUser)
)
const modules = computed(() => {
if (activeTab.value === 'INSTALLED') {
return moduleStore.modules.filter((_m) => _m.installed)
}
return moduleStore.modules
})
async function fetchModulesData() {
isFetchingModule.value = true
await moduleStore.fetchModules().then(() => {
isFetchingModule.value = false
})
}
async function submitApiToken() {
v$.value.$touch()
if (v$.value.$invalid) {
return true
}
isSaving.value = true
moduleStore
.checkApiToken(moduleStore.currentUser.api_token)
.then((response) => {
if (response.data.success) {
saveApiTokenToSettings()
return
}
isSaving.value = false
return
})
}
async function saveApiTokenToSettings() {
try {
await globalStore
.updateGlobalSettings({
data: {
settings: {
api_token: moduleStore.currentUser.api_token,
},
},
message: 'settings.preferences.updated_message',
})
.then((response) => {
if (response.data.success) {
moduleStore.apiToken = moduleStore.currentUser.api_token
return
}
})
isSaving.value = false
} catch (err) {
isSaving.value = false
console.error(err)
return
}
}
function setStatusFilter(data) {
activeTab.value = data.filter
}
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
<template>
<div
class="
relative
shadow-md
border-2 border-gray-200 border-opacity-60
rounded-lg
cursor-pointer
overflow-hidden
h-100
"
@click="$router.push(`/admin/modules/${data.slug}`)"
>
<div
v-if="data.purchased"
class="absolute mt-5 px-6 w-full flex justify-end"
>
<label
v-if="data.purchased"
class="
bg-white bg-opacity-75
text-xs
px-3
py-1
font-semibold
tracking-wide
rounded
"
>
{{ $t('modules.purchased') }}
</label>
<label
v-if="data.installed"
class="
ml-2
bg-white bg-opacity-75
text-xs
px-3
py-1
font-semibold
tracking-wide
rounded
"
>
<span v-if="data.update_available">
{{ $t('modules.update_available') }}
</span>
<span v-else>
{{ $t('modules.installed') }}
</span>
</label>
</div>
<img
class="lg:h-64 md:h-48 w-full object-cover object-center"
:src="data.cover"
alt="cover"
/>
<div class="px-6 py-5 flex flex-col bg-gray-50 flex-1 justify-between">
<span
class="
text-lg
sm:text-2xl
font-medium
whitespace-nowrap
truncate
text-primary-500
"
>
{{ data.name }}
</span>
<div v-if="data.author_avatar" class="flex items-center mt-2">
<img
class="hidden h-10 w-10 rounded-full sm:inline-block mr-2"
:src="
data.author_avatar
? data.author_avatar
: 'http://localhost:3000/img/default-avatar.jpg'
"
alt=""
/>
<span>by</span>
<span class="ml-2 text-base font-semibold truncate"
>{{ data.author_name }}
</span>
</div>
<base-text
:text="data.short_description"
class="pt-4 text-gray-500 h-16 line-clamp-2"
:length="110"
>
</base-text>
<div
class="
flex
justify-between
mt-4
flex-col
space-y-2
sm:space-y-0 sm:flex-row
"
>
<div><BaseRating :rating="averageRating" /></div>
<div
class="
text-xl
md:text-2xl
font-semibold
whitespace-nowrap
text-primary-500
"
>
$
{{
data.monthly_price
? data.monthly_price / 100
: data.yearly_price / 100
}}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { computed, onMounted, ref, watch, reactive } from 'vue'
const { t } = useI18n()
const props = defineProps({
data: {
type: Object,
default: null,
required: true,
},
})
let averageRating = computed(() => {
return parseInt(props.data.average_rating)
})
</script>

View File

@ -0,0 +1,49 @@
<template>
<BaseContentPlaceholders>
<div
class="
shadow-md
border-2 border-gray-200 border-opacity-60
rounded-lg
cursor-pointer
overflow-hidden
h-100
"
>
<BaseContentPlaceholdersBox class="h-48 lg:h-64 md:h-48 w-full" rounded />
<div class="px-6 py-5 flex flex-col bg-gray-50 flex-1 justify-between">
<BaseContentPlaceholdersText class="w-32 h-8" :lines="1" rounded />
<div class="flex items-center mt-2">
<BaseContentPlaceholdersBox
class="h-10 w-10 rounded-full sm:inline-block mr-2"
/>
<div>
<BaseContentPlaceholdersText
class="w-32 h-8 ml-2"
:lines="1"
rounded
/>
</div>
</div>
<BaseContentPlaceholdersText
class="pt-4 w-full h-16"
:lines="1"
rounded
/>
<div
class="
flex
justify-between
mt-4
flex-col
space-y-2
sm:space-y-0 sm:flex-row
"
>
<BaseContentPlaceholdersText class="w-32 h-8" :lines="1" rounded />
<BaseContentPlaceholdersText class="w-32 h-8" :lines="1" rounded />
</div>
</div>
</div>
</BaseContentPlaceholders>
</template>

View File

@ -0,0 +1,104 @@
<template>
<BaseContentPlaceholders rounded>
<BasePage class="bg-white">
<!-- Breadcrumb-->
<BaseContentPlaceholdersText class="mt-4 h-8 w-40" :lines="1"/>
<BaseContentPlaceholdersText class="mt-4 h-8 w-56 mb-4" :lines="1"/>
<!-- Product -->
<div class="lg:grid lg:grid-rows-1 lg:grid-cols-7 lg:gap-x-8 lg:gap-y-10 xl:gap-x-16 mt-6">
<!-- Product image -->
<div class="lg:row-end-1 lg:col-span-4">
<BaseContentPlaceholdersBox class="h-96 sm:w-full" rounded />
</div>
<!-- Product details -->
<div
class="
max-w-2xl
mx-auto
mt-10
lg:max-w-none lg:mt-0 lg:row-end-2 lg:row-span-2 lg:col-span-3
w-full
"
>
<!-- Average Rating -->
<div>
<h3 class="sr-only">Reviews</h3>
<BaseContentPlaceholdersText class="w-32 h-8" :lines="1" />
<p class="sr-only">4 out of 5 stars</p>
</div>
<!-- Module Name and Version -->
<div class="flex flex-col-reverse">
<div class="mt-4">
<BaseContentPlaceholdersText
class="w-48 xl:w-80 h-12"
:lines="1"
/>
<BaseContentPlaceholdersText
class="w-64 xl:w-80 h-8 mt-2"
:lines="1"
/>
</div>
</div>
<!-- Module Description -->
<div>
<BaseContentPlaceholdersText
class="w-full h-24 my-10"
:lines="1"
/>
</div>
<!-- Module Pricing -->
<div>
<BaseContentPlaceholdersText
class="w-full h-24 mt-6 mb-6"
:lines="1"
/>
</div>
<!-- Button Section -->
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2">
<BaseContentPlaceholdersText class="w-full h-14" :lines="1" />
</div>
<div class="mt-10"></div>
<!-- HightLight -->
<div class="border-t border-gray-200 mt-10 pt-10">
<div>
<BaseContentPlaceholdersText class="w-24 h-6" :lines="1" />
<BaseContentPlaceholdersText
class="mt-4 w-full h-20"
:lines="1"
/>
</div>
</div>
<!-- Social Share -->
<div class="border-t border-gray-200 mt-10 pt-10">
<BaseContentPlaceholdersText class="h-6 w-24" :lines="1" />
<BaseContentPlaceholdersText class="h-10 w-32 mt-4" :lines="1" />
</div>
</div>
<div
class="
w-full
max-w-2xl
mx-auto
mt-16
lg:max-w-none lg:mt-0 lg:col-span-4
"
>
<BaseContentPlaceholdersBox class="h-96 sm:w-full" rounded />
</div>
</div>
</BasePage>
</BaseContentPlaceholders>
</template>

View File

@ -0,0 +1,63 @@
<template>
<router-link class="relative group" :to="`/admin/modules/${data.slug}`">
<div class="relative group">
<div class="aspect-w-4 aspect-h-3 rounded-lg overflow-hidden bg-gray-100">
<img :src="data.cover" class="object-center object-cover" />
<div
class="flex items-end opacity-0 p-4 group-hover:opacity-100"
aria-hidden="true"
>
<div
class="
w-full
bg-white bg-opacity-75
backdrop-filter backdrop-blur
py-2
px-4
rounded-md
text-sm
font-medium
text-primary-500 text-center
"
>
{{ $t('modules.view_module') }}
</div>
</div>
</div>
<div
class="
mt-4
flex
items-center
justify-between
text-base
font-medium
text-gray-900
space-x-8
cursor-pointer
"
>
<h3 class="text-primary-500 font-bold">
<span aria-hidden="true" class="absolute inset-0"></span>
{{ data.name }}
</h3>
<p class="text-primary-500 font-bold">
$ {{ data.monthly_price / 100 }}
</p>
</div>
</div>
</router-link>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
data: {
type: Object,
default: null,
required: true,
},
})
</script>