fix: logout issue
This commit is contained in:
@@ -22,7 +22,6 @@
|
||||
|
||||
<script setup>
|
||||
import UserDropdown from '@/components/UserDropdown.vue'
|
||||
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
|
||||
import SidebarLink from '@/components/SidebarLink.vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
<template>
|
||||
<div class="shadow rounded-md">
|
||||
<div>
|
||||
<div class="shadow rounded-md p-4 h-full" style="min-height: 150px;">
|
||||
<div class="text-xl font-semibold mb-1">
|
||||
{{ batch.title }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="short-introduction">
|
||||
{{ batch.description }}
|
||||
</div>
|
||||
<div>
|
||||
<Calendar class="h-4 w-4 stroke-1" />
|
||||
{{ batch.start_date }} - {{ batch.end_date }}
|
||||
</div>
|
||||
<div>
|
||||
<Clock class="h-4 w-4 stroke-1" />
|
||||
{{ batch.start_time }} - {{ batch.end_time }}
|
||||
<div class="mt-auto">
|
||||
<div class="flex items-center mb-1">
|
||||
<Calendar class="h-4 w-4 stroke-1 mr-2" />
|
||||
{{ dayjs(batch.start_date).format("DD MMM YYYY") }} - {{ dayjs(batch.end_date).format("DD MMM YYYY") }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Clock class="h-4 w-4 stroke-1 mr-2" />
|
||||
{{ batch.start_time }} - {{ batch.end_time }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Calendar, Clock } from "lucide-vue-next"
|
||||
import { inject } from "vue"
|
||||
|
||||
const dayjs = inject("$dayjs")
|
||||
const props = defineProps({
|
||||
batch: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
<style>
|
||||
.short-introduction {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0.25rem 0 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
@@ -69,8 +69,7 @@ import { computed } from 'vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
|
||||
const { isLoggedIn, getUser } = sessionStore()
|
||||
const user = computed(() => isLoggedIn && getUser())
|
||||
const { isLoggedIn, user } = sessionStore()
|
||||
|
||||
const props = defineProps({
|
||||
course: {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="mt-4">
|
||||
<Disclosure v-slot="{ open }" v-for="chapter in outline.data" :key="chapter.name">
|
||||
<DisclosureButton
|
||||
class="flex w-full px-2 py-4"
|
||||
class="flex w-full px-2 pt-2 pb-2"
|
||||
>
|
||||
<ChevronUp
|
||||
:class="open ? 'rotate-180 transform' : ''"
|
||||
@@ -16,9 +16,9 @@
|
||||
{{ chapter.title }}
|
||||
</div>
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel class="px-10 pb-4">
|
||||
<DisclosurePanel class="px-10 pb-2">
|
||||
<div v-for="lesson in chapter.lessons" :key="lesson.name">
|
||||
<div class="flex items-center text-lg mb-2">
|
||||
<div class="flex items-center text-lg mb-4">
|
||||
<MonitorPlay v-if="lesson.icon === 'icon-youtube'" class="h-4 w-4 text-gray-900 stroke-1 mr-2"/>
|
||||
<HelpCircle v-else-if="lesson.icon === 'icon-quiz'" class="h-4 w-4 text-gray-900 stroke-1 mr-2"/>
|
||||
<FileText v-else-if="lesson.icon === 'icon-list'" class="h-4 w-4 text-gray-900 stroke-1 mr-2"/>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
LMS
|
||||
</div>
|
||||
<div v-if="user" class="mt-1 text-sm text-gray-700 leading-none">
|
||||
{{ user.full_name }}
|
||||
{{ convertToTitleCase(user.split('@')[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="duration-300 ease-in-out" :class="isCollapsed
|
||||
@@ -35,7 +35,6 @@ import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { Dropdown } from 'frappe-ui'
|
||||
import { ChevronDown } from 'lucide-vue-next'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
isCollapsed: {
|
||||
@@ -44,9 +43,9 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const { getUser, logout } = sessionStore()
|
||||
const { logout, user } = sessionStore()
|
||||
let { isLoggedIn } = sessionStore();
|
||||
const user = computed(() => isLoggedIn && getUser())
|
||||
|
||||
const userDropdownOptions = [
|
||||
{
|
||||
icon: 'log-out',
|
||||
@@ -70,5 +69,15 @@ const userDropdownOptions = [
|
||||
return !isLoggedIn
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
function convertToTitleCase(str) {
|
||||
if (!str) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return str.toLowerCase().split(' ').map(function (word) {
|
||||
return word.charAt(0).toUpperCase().concat(word.substr(1));
|
||||
}).join(' ');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,21 +4,15 @@ import { createApp } from 'vue'
|
||||
import router from './router'
|
||||
import App from './App.vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import dayjs from '@/utils/dayjs'
|
||||
import translationPlugin from './translation'
|
||||
import { usersStore } from './stores/user'
|
||||
import { sessionStore } from './stores/session'
|
||||
|
||||
import {
|
||||
FrappeUI,
|
||||
Button,
|
||||
setConfig,
|
||||
frappeRequest,
|
||||
resourcesPlugin,
|
||||
} from 'frappe-ui'
|
||||
import { FrappeUI, setConfig, frappeRequest, resourcesPlugin } from 'frappe-ui'
|
||||
|
||||
// create a pinia instance
|
||||
let pinia = createPinia()
|
||||
|
||||
let app = createApp(App)
|
||||
|
||||
setConfig('resourceFetcher', frappeRequest)
|
||||
|
||||
app.use(FrappeUI)
|
||||
@@ -26,6 +20,16 @@ app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(resourcesPlugin)
|
||||
app.use(translationPlugin)
|
||||
app.provide('$dayjs', dayjs)
|
||||
|
||||
app.component('Button', Button)
|
||||
app.mount('#app')
|
||||
|
||||
const { userResource } = usersStore()
|
||||
let { isLoggedIn } = sessionStore()
|
||||
|
||||
if (isLoggedIn) {
|
||||
await userResource.reload()
|
||||
}
|
||||
|
||||
app.provide('$user', userResource)
|
||||
app.config.globalProperties.$user = userResource
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="h-screen">
|
||||
<div class="h-screen text-base">
|
||||
<header
|
||||
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
</header>
|
||||
<div class="mx-5 my-10">
|
||||
<div class="grid grid-cols-3 gap-8 mt-5">
|
||||
<div class="grid grid-cols-4 gap-8 mt-5">
|
||||
<BatchCard v-for="batch in batches.data" :batch="batch" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,28 @@
|
||||
<template>
|
||||
<div class="h-screen">
|
||||
<header
|
||||
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
<Breadcrumbs
|
||||
class="h-7"
|
||||
:items="[{ label: __('All Courses'), route: { name: 'Courses' } }]"
|
||||
/>
|
||||
<div class="flex">
|
||||
<Select class="mr-2"
|
||||
:options="orderOptions"
|
||||
v-model="orderBy"
|
||||
/>
|
||||
<Button variant="solid">
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4" />
|
||||
</template>
|
||||
{{ __("New Course") }}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mx-5 my-10">
|
||||
<div v-if="courses.data" class="h-screen">
|
||||
<header class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5">
|
||||
<Breadcrumbs class="h-7" :items="[{ label: __('All Courses'), route: { name: 'Courses' } }]" />
|
||||
<div class="flex">
|
||||
<Select class="mr-2" :options="orderOptions" v-model="orderBy" />
|
||||
<Button variant="solid">
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4" />
|
||||
</template>
|
||||
{{ __("New Course") }}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mx-5 py-5">
|
||||
<Tabs class="overflow-hidden" v-model="tabIndex" :tabs="tabs">
|
||||
<template #tab="{ tab, selected }">
|
||||
<div>
|
||||
<button
|
||||
class="group -mb-px flex items-center gap-2 border-b border-transparent py-2.5 text-base text-gray-600 duration-300 ease-in-out hover:border-gray-400 hover:text-gray-900"
|
||||
:class="{ 'text-gray-900': selected }"
|
||||
>
|
||||
:class="{ 'text-gray-900': selected }">
|
||||
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
|
||||
{{ __(tab.label) }}
|
||||
<Badge
|
||||
:class="{ 'text-gray-900 border border-gray-900': selected }"
|
||||
variant="subtle"
|
||||
theme="gray"
|
||||
size="sm"
|
||||
>
|
||||
<Badge :class="{ 'text-gray-900 border border-gray-900': selected }" variant="subtle" theme="gray"
|
||||
size="sm">
|
||||
{{ tab.count }}
|
||||
</Badge>
|
||||
</button>
|
||||
@@ -43,7 +30,8 @@
|
||||
</template>
|
||||
<template #default="{ tab }">
|
||||
<div v-if="tab.courses && tab.courses.value.length" class="grid grid-cols-3 gap-8 mt-5">
|
||||
<router-link v-for="course in tab.courses.value" :to="{ name: 'CourseDetail', params: { courseName: course.name } }">
|
||||
<router-link v-for="course in tab.courses.value"
|
||||
:to="{ name: 'CourseDetail', params: { courseName: course.name } }">
|
||||
<CourseCard :course="course" />
|
||||
</router-link>
|
||||
</div>
|
||||
@@ -58,39 +46,34 @@
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { createListResource, Breadcrumbs, Tabs, Badge, Select } from 'frappe-ui';
|
||||
import { createListResource, Breadcrumbs, Tabs, Badge, Select, Button } from 'frappe-ui';
|
||||
import CourseCard from '@/components/CourseCard.vue';
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, inject } from 'vue'
|
||||
|
||||
const user = inject("$user")
|
||||
|
||||
const { isLoggedIn, getUser } = sessionStore()
|
||||
const user = computed(() => isLoggedIn && getUser())
|
||||
console.log(user)
|
||||
const courses = createListResource({
|
||||
type: 'list',
|
||||
cache: ["courses", user.email],
|
||||
doctype: 'LMS Course',
|
||||
cache: ["courses", user?.data?.email],
|
||||
url: "lms.lms.utils.get_courses",
|
||||
auto: true,
|
||||
});
|
||||
|
||||
const is_moderator = computed(() => {
|
||||
if (user && user.value?.roles?.includes('Moderator')) {
|
||||
if (user.data?.roles?.includes('Moderator')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const is_instructor = computed(() => {
|
||||
if (user && user.value?.roles?.includes('Course Creator')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return user.data.roles.includes("Course Creator") ? true : false;
|
||||
});
|
||||
|
||||
const tabIndex = ref(0)
|
||||
@@ -106,8 +89,8 @@ const tabs = [
|
||||
count: computed(() => courses.data?.upcoming?.length),
|
||||
}
|
||||
];
|
||||
|
||||
if (user.value) {
|
||||
console.log(user.data)
|
||||
if (user.data) {
|
||||
tabs.push({
|
||||
label: 'Enrolled',
|
||||
courses: computed(() => courses.data?.enrolled),
|
||||
@@ -151,19 +134,4 @@ const orderOptions = [
|
||||
},
|
||||
];
|
||||
const orderBy = 'enrollment';
|
||||
|
||||
function sort_courses(order) {
|
||||
const categories = ['live', 'upcoming', 'enrolled', 'created', 'under_review'];
|
||||
categories.forEach(category => {
|
||||
courses.data[category] = courses.data[category].sort((a, b) => {
|
||||
if (order === 'enrollment') {
|
||||
return b.enrollment_count - a.enrollment_count;
|
||||
} else if (order === 'rating') {
|
||||
return b.avg_rating - a.avg_rating;
|
||||
} else if (order === 'newest') {
|
||||
return new Date(b.creation).getTime() - new Date(a.creation).getTime();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -3,7 +3,9 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { createResource, Button } from "frappe-ui";
|
||||
|
||||
import { useRoute } from "vue-router";
|
||||
const route = useRoute();
|
||||
console.log(route)
|
||||
const props = defineProps({
|
||||
courseName: {
|
||||
type: String,
|
||||
@@ -14,7 +16,7 @@ const props = defineProps({
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
const lesson = createResource({
|
||||
url: "lms.lms.utils.get_lesson",
|
||||
cache: ["lesson", props.courseName, props.lessonNumber],
|
||||
@@ -23,5 +25,5 @@ const lesson = createResource({
|
||||
lesson: props.lessonNumber,
|
||||
},
|
||||
auto: true,
|
||||
});
|
||||
}); */
|
||||
</script>
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { usersStore } from './stores/user'
|
||||
import { sessionStore } from './stores/session'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -19,10 +21,10 @@ const routes = [
|
||||
},
|
||||
{
|
||||
// Create a route for path /courses/inventory-management/learn/1.1
|
||||
path: '/courses/:courseName/learn/:chapterId',
|
||||
path: '/courses/:courseName/learn/:lessonNumber',
|
||||
name: 'Lesson',
|
||||
component: () => import('@/pages/Lesson.vue'),
|
||||
props: true,
|
||||
props: {},
|
||||
},
|
||||
{
|
||||
path: '/batches',
|
||||
@@ -36,4 +38,18 @@ let router = createRouter({
|
||||
routes,
|
||||
})
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const { userResource } = usersStore()
|
||||
let { isLoggedIn } = sessionStore()
|
||||
|
||||
try {
|
||||
if (isLoggedIn) {
|
||||
await userResource.reload()
|
||||
}
|
||||
} catch (error) {
|
||||
isLoggedIn = false
|
||||
}
|
||||
return next()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -5,9 +5,9 @@ import router from '@/router'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export const sessionStore = defineStore('lms-session', () => {
|
||||
const { user, usersByName } = usersStore()
|
||||
let { userResource } = usersStore()
|
||||
|
||||
function currentUser() {
|
||||
function sessionUser() {
|
||||
let cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||
let _sessionUser = cookies.get('user_id')
|
||||
if (_sessionUser === 'Guest') {
|
||||
@@ -16,18 +16,8 @@ export const sessionStore = defineStore('lms-session', () => {
|
||||
return _sessionUser
|
||||
}
|
||||
|
||||
let sessionUser = ref(currentUser())
|
||||
const isLoggedIn = ref(!!sessionUser.value)
|
||||
|
||||
function getUser() {
|
||||
if (!sessionUser.value) {
|
||||
return null
|
||||
}
|
||||
if (usersByName[sessionUser.value]) {
|
||||
return usersByName[sessionUser.value]
|
||||
}
|
||||
return user.value
|
||||
}
|
||||
let user = ref(sessionUser())
|
||||
const isLoggedIn = computed(() => !!user.value)
|
||||
|
||||
const login = createResource({
|
||||
url: 'login',
|
||||
@@ -35,8 +25,8 @@ export const sessionStore = defineStore('lms-session', () => {
|
||||
throw new Error('Invalid email or password')
|
||||
},
|
||||
onSuccess() {
|
||||
user.reload()
|
||||
sessionUser.value = currentUser()
|
||||
userResource.reload()
|
||||
user.value = sessionUser()
|
||||
login.reset()
|
||||
router.replace({ path: '/' })
|
||||
},
|
||||
@@ -45,16 +35,16 @@ export const sessionStore = defineStore('lms-session', () => {
|
||||
const logout = createResource({
|
||||
url: 'logout',
|
||||
onSuccess() {
|
||||
user.reset()
|
||||
sessionUser.value = null
|
||||
userResource.reset()
|
||||
user.value = null
|
||||
window.location.reload()
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
sessionUser,
|
||||
user,
|
||||
isLoggedIn,
|
||||
login,
|
||||
logout,
|
||||
getUser,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { createResource } from 'frappe-ui'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const usersStore = defineStore('lms-users', () => {
|
||||
let usersByName = reactive({})
|
||||
|
||||
const user = createResource({
|
||||
let userResource = createResource({
|
||||
url: 'lms.lms.api.get_user_info',
|
||||
cache: 'Users',
|
||||
initialData: [],
|
||||
auto: true,
|
||||
transform: (data) => {
|
||||
if (data?.name && !usersByName[data.name]) {
|
||||
usersByName[data.name] = data
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
if (error && error.exc_type === 'AuthenticationError') {
|
||||
router.push('/login')
|
||||
@@ -23,7 +12,6 @@ export const usersStore = defineStore('lms-users', () => {
|
||||
})
|
||||
|
||||
return {
|
||||
user,
|
||||
usersByName,
|
||||
userResource,
|
||||
}
|
||||
})
|
||||
|
||||
12
frontend/src/utils/dayjs.js
Normal file
12
frontend/src/utils/dayjs.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import dayjs from 'dayjs/esm'
|
||||
import relativeTime from 'dayjs/esm/plugin/relativeTime'
|
||||
import localizedFormat from 'dayjs/esm/plugin/localizedFormat'
|
||||
import updateLocale from 'dayjs/esm/plugin/updateLocale'
|
||||
import isToday from 'dayjs/esm/plugin/isToday'
|
||||
|
||||
dayjs.extend(updateLocale)
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.extend(localizedFormat)
|
||||
dayjs.extend(isToday)
|
||||
|
||||
export default dayjs
|
||||
Reference in New Issue
Block a user