feat: read only mode

This commit is contained in:
Jannat Patel
2025-04-30 18:03:00 +05:30
parent 0d32c2a9d9
commit 603e80fd26
26 changed files with 427 additions and 302 deletions

View File

@@ -26,13 +26,8 @@
<a href="{{ meta.link }}">Know More</a> <a href="{{ meta.link }}">Know More</a>
</div> </div>
</div> </div>
<div id="modals"></div>
<div id="popovers"></div>
<script> <script>
document.getElementById('seo-content').style.display = 'none'; document.getElementById('seo-content').style.display = 'none';
window.csrf_token = '{{ csrf_token }}'
window.read_only_mode = '{{ read_only }}'
</script> </script>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>

View File

@@ -39,7 +39,11 @@
{{ __('More') }} {{ __('More') }}
</span> </span>
</div> </div>
<Button v-if="isModerator" variant="ghost" @click="openPageModal()"> <Button
v-if="isModerator && !readOnlyMode"
variant="ghost"
@click="openPageModal()"
>
<template #icon> <template #icon>
<Plus class="h-4 w-4 text-ink-gray-7 stroke-1.5" /> <Plus class="h-4 w-4 text-ink-gray-7 stroke-1.5" />
</template> </template>
@@ -63,6 +67,16 @@
</div> </div>
</div> </div>
<div class="m-2 flex flex-col gap-1"> <div class="m-2 flex flex-col gap-1">
<div
v-if="readOnlyMode && !sidebarStore.isSidebarCollapsed"
class="z-10 m-2 bg-surface-modal py-2.5 px-3 text-xs text-ink-gray-7 leading-5 rounded-md"
>
{{
__(
'This site is being updated. You will not be able to make any changes. Full access will be restored shortly.'
)
}}
</div>
<TrialBanner <TrialBanner
v-if=" v-if="
userResource.data?.is_system_manager && userResource.data?.is_fc_site userResource.data?.is_system_manager && userResource.data?.is_fc_site
@@ -89,15 +103,31 @@
: 'flex-row space-x-3' : 'flex-row space-x-3'
" "
> >
<Tooltip v-if="readOnlyMode && sidebarStore.isSidebarCollapsed">
<CircleAlert
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
/>
<template #body>
<div
class="max-w-[30ch] rounded bg-surface-gray-7 px-2 py-1 text-center text-p-xs text-ink-white shadow-xl"
>
{{
__(
'This site is being updated. You will not be able to make any changes. Full access will be restored shortly.'
)
}}
</div>
</template>
</Tooltip>
<Tooltip :text="__('Powered by Learning')"> <Tooltip :text="__('Powered by Learning')">
<Zap <Zap
class="size-4 stroke-1.5 text-gray-700 cursor-pointer" class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
@click="redirectToWebsite()" @click="redirectToWebsite()"
/> />
</Tooltip> </Tooltip>
<Tooltip :text="__('Help')"> <Tooltip :text="__('Help')">
<CircleHelp <CircleHelp
class="size-4 stroke-1.5 text-gray-700 cursor-pointer" class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
@click=" @click="
() => { () => {
showHelpModal = minimize ? true : !showHelpModal showHelpModal = minimize ? true : !showHelpModal
@@ -113,7 +143,7 @@
" "
> >
<CollapseSidebar <CollapseSidebar
class="size-4 text-gray-700 duration-300 stroke-1.5 ease-in-out cursor-pointer" class="size-4 text-ink-gray-7 duration-300 stroke-1.5 ease-in-out cursor-pointer"
:class="{ :class="{
'[transform:rotateY(180deg)]': sidebarStore.isSidebarCollapsed, '[transform:rotateY(180deg)]': sidebarStore.isSidebarCollapsed,
}" }"
@@ -166,6 +196,7 @@ import { useRouter } from 'vue-router'
import InviteIcon from './Icons/InviteIcon.vue' import InviteIcon from './Icons/InviteIcon.vue'
import { import {
BookOpen, BookOpen,
CircleAlert,
ChevronRight, ChevronRight,
Plus, Plus,
CircleHelp, CircleHelp,
@@ -203,6 +234,7 @@ const currentStep = ref({})
const router = useRouter() const router = useRouter()
let onboardingDetails let onboardingDetails
let isOnboardingStepsCompleted = false let isOnboardingStepsCompleted = false
const readOnlyMode = window.read_only_mode
const iconProps = { const iconProps = {
strokeWidth: 1.5, strokeWidth: 1.5,
width: 16, width: 16,

View File

@@ -4,7 +4,7 @@
<div class="text-lg font-semibold text-ink-gray-9"> <div class="text-lg font-semibold text-ink-gray-9">
{{ __('Assessments') }} {{ __('Assessments') }}
</div> </div>
<Button v-if="canSeeAddButton()" @click="showModal = true"> <Button v-if="canAddAssessments()" @click="showModal = true">
<template #prefix> <template #prefix>
<Plus class="h-4 w-4" /> <Plus class="h-4 w-4" />
</template> </template>
@@ -100,6 +100,7 @@ import { Plus, Trash2 } from 'lucide-vue-next'
const user = inject('$user') const user = inject('$user')
const showModal = ref(false) const showModal = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
batch: { batch: {
@@ -181,7 +182,8 @@ const getRowRoute = (row) => {
} }
} }
const canSeeAddButton = () => { const canAddAssessments = () => {
if (readOnlyMode) return false
return user.data?.is_moderator || user.data?.is_evaluator return user.data?.is_moderator || user.data?.is_evaluator
} }

View File

@@ -89,6 +89,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { Plus, Trash2 } from 'lucide-vue-next' import { Plus, Trash2 } from 'lucide-vue-next'
import { showToast } from '@/utils' import { showToast } from '@/utils'
const readOnlyMode = window.read_only_mode
const showCourseModal = ref(false) const showCourseModal = ref(false)
const user = inject('$user') const user = inject('$user')
@@ -159,6 +160,9 @@ const removeCourses = (selections, unselectAll) => {
} }
const canSeeAddButton = () => { const canSeeAddButton = () => {
if (readOnlyMode) {
return false
}
return user.data?.is_moderator || user.data?.is_evaluator return user.data?.is_moderator || user.data?.is_evaluator
} }
</script> </script>

View File

@@ -111,7 +111,6 @@ import {
FormControl, FormControl,
ListView, ListView,
ListHeader, ListHeader,
ListHeaderItem,
ListRows, ListRows,
ListRow, ListRow,
ListRowItem, ListRowItem,

View File

@@ -49,6 +49,7 @@
{{ batch.data.timezone }} {{ batch.data.timezone }}
</span> </span>
</div> </div>
<div v-if="!readOnlyMode">
<router-link <router-link
v-if="isModerator || isStudent" v-if="isModerator || isStudent"
:to="{ :to="{
@@ -112,6 +113,7 @@
</Button> </Button>
</router-link> </router-link>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { inject, computed } from 'vue' import { inject, computed } from 'vue'
@@ -123,7 +125,7 @@ import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const user = inject('$user') const user = inject('$user')
const dayjs = inject('$dayjs') const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
batch: { batch: {

View File

@@ -110,7 +110,7 @@
<div class="text-ink-gray-7 font-medium"> <div class="text-ink-gray-7 font-medium">
{{ __('Students') }} {{ __('Students') }}
</div> </div>
<Button @click="openStudentModal()"> <Button v-if="!readOnlyMode" @click="openStudentModal()">
<template #prefix> <template #prefix>
<Plus class="h-4 w-4" /> <Plus class="h-4 w-4" />
</template> </template>
@@ -247,6 +247,7 @@ const chartData = ref(null)
const chartOptions = ref(null) const chartOptions = ref(null)
const showProgressChart = ref(false) const showProgressChart = ref(false)
const assessmentCount = ref(0) const assessmentCount = ref(0)
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
batch: { batch: {

View File

@@ -9,6 +9,7 @@
<div v-if="course.data.paid_course" class="text-2xl font-semibold mb-3"> <div v-if="course.data.paid_course" class="text-2xl font-semibold mb-3">
{{ course.data.price }} {{ course.data.price }}
</div> </div>
<div v-if="!readOnlyMode">
<div v-if="course.data.membership" class="space-y-2"> <div v-if="course.data.membership" class="space-y-2">
<router-link <router-link
:to="{ :to="{
@@ -90,8 +91,12 @@
</span> </span>
</Button> </Button>
</router-link> </router-link>
</div>
<div class="space-y-4"> <div class="space-y-4">
<div class="mt-8 font-medium text-ink-gray-9"> <div
class="font-medium text-ink-gray-9"
:class="{ 'mt-8': !readOnlyMode }"
>
{{ __('This course has:') }} {{ __('This course has:') }}
</div> </div>
<div class="flex items-center text-ink-gray-9"> <div class="flex items-center text-ink-gray-9">
@@ -149,6 +154,7 @@ import CertificationLinks from '@/components/CertificationLinks.vue'
const router = useRouter() const router = useRouter()
const user = inject('$user') const user = inject('$user')
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
course: { course: {

View File

@@ -8,18 +8,6 @@
<AppSidebar /> <AppSidebar />
</div> </div>
<div class="w-full overflow-auto" id="scrollContainer"> <div class="w-full overflow-auto" id="scrollContainer">
<div
v-if="readOnlyMode"
class="right-0 top-0 mb-3 bg-surface-gray-2 py-3 text-sm text-ink-gray-5"
>
<div class="mx-auto px-10">
{{
__(
'This site is running in read-only mode. Full functionality will be restored soon.'
)
}}
</div>
</div>
<slot /> <slot />
</div> </div>
</div> </div>
@@ -28,6 +16,4 @@
</template> </template>
<script setup> <script setup>
import AppSidebar from './AppSidebar.vue' import AppSidebar from './AppSidebar.vue'
const readOnlyMode = window.read_only_mode
</script> </script>

View File

@@ -27,7 +27,9 @@
</span> </span>
</div> </div>
<Dropdown <Dropdown
v-if="user.data.name == reply.owner && !reply.editable" v-if="
user.data.name == reply.owner && !reply.editable && !readOnlyMode
"
:options="[ :options="[
{ {
label: 'Edit', label: 'Edit',
@@ -71,7 +73,7 @@
</div> </div>
<TextEditor <TextEditor
v-if="renderEditor" v-if="renderEditor && !readOnlyMode"
class="mt-5" class="mt-5"
:content="newReply" :content="newReply"
:mentions="mentionUsers" :mentions="mentionUsers"
@@ -80,7 +82,7 @@
:fixedMenu="true" :fixedMenu="true"
editorClass="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-outline-gray-2 prose-th:border-outline-gray-2 prose-td:relative prose-th:relative prose-th:bg-surface-gray-2 prose-sm max-w-none border border-outline-gray-2 rounded-b-md min-h-[7rem] py-1 px-2" editorClass="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-outline-gray-2 prose-th:border-outline-gray-2 prose-td:relative prose-th:relative prose-th:bg-surface-gray-2 prose-sm max-w-none border border-outline-gray-2 rounded-b-md min-h-[7rem] py-1 px-2"
/> />
<div class="flex justify-between mt-2"> <div v-if="!readOnlyMode" class="flex justify-between mt-2">
<span> </span> <span> </span>
<Button @click="postReply()"> <Button @click="postReply()">
<span> <span>
@@ -105,6 +107,7 @@ const user = inject('$user')
const allUsers = inject('$allUsers') const allUsers = inject('$allUsers')
const mentionUsers = ref([]) const mentionUsers = ref([])
const renderEditor = ref(false) const renderEditor = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
topic: { topic: {

View File

@@ -1,6 +1,10 @@
<template> <template>
<div> <div>
<Button v-if="!singleThread" class="float-right" @click="openTopicModal()"> <Button
v-if="!singleThread && !readOnlyMode"
class="float-right"
@click="openTopicModal()"
>
{{ __('New {0}').format(singularize(title)) }} {{ __('New {0}').format(singularize(title)) }}
</Button> </Button>
<div class="text-xl font-semibold text-ink-gray-9"> <div class="text-xl font-semibold text-ink-gray-9">
@@ -77,6 +81,7 @@ const currentTopic = ref(null)
const socket = inject('$socket') const socket = inject('$socket')
const user = inject('$user') const user = inject('$user')
const showTopicModal = ref(false) const showTopicModal = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
title: { title: {

View File

@@ -3,7 +3,7 @@
<div class="text-lg font-semibold text-ink-gray-9"> <div class="text-lg font-semibold text-ink-gray-9">
{{ __('Live Class') }} {{ __('Live Class') }}
</div> </div>
<Button v-if="user.data.is_moderator" @click="openLiveClassModal"> <Button v-if="canCreateClass()" @click="openLiveClassModal">
<template #prefix> <template #prefix>
<Plus class="h-4 w-4" /> <Plus class="h-4 w-4" />
</template> </template>
@@ -87,6 +87,7 @@ import { formatTime } from '@/utils/'
const user = inject('$user') const user = inject('$user')
const showLiveClassModal = ref(false) const showLiveClassModal = ref(false)
const dayjs = inject('$dayjs') const dayjs = inject('$dayjs')
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
batch: { batch: {
@@ -116,6 +117,11 @@ const liveClasses = createListResource({
const openLiveClassModal = () => { const openLiveClassModal = () => {
showLiveClassModal.value = true showLiveClassModal.value = true
} }
const canCreateClass = () => {
if (readOnlyMode) return false
return user.data?.is_moderator || user.data?.is_evaluator
}
</script> </script>
<style> <style>
.short-introduction { .short-introduction {

View File

@@ -29,4 +29,3 @@ app.provide('$allUsers', allUsers)
app.config.globalProperties.$user = userResource app.config.globalProperties.$user = userResource
app.config.globalProperties.$dialog = createDialog app.config.globalProperties.$dialog = createDialog
app.config.globalProperties.readOnlyMode = window.read_only_mode

View File

@@ -4,6 +4,7 @@
> >
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
<Button <Button
v-if="!readOnlyMode"
variant="solid" variant="solid"
@click=" @click="
() => { () => {
@@ -38,6 +39,7 @@
showTooltip: false, showTooltip: false,
selectable: false, selectable: false,
onRowClick: (row) => { onRowClick: (row) => {
if (readOnlyMode) return
assignmentID = row.name assignmentID = row.name
showAssignmentForm = true showAssignmentForm = true
}, },
@@ -98,6 +100,7 @@ const showAssignmentForm = ref(false)
const assignmentID = ref('new') const assignmentID = ref('new')
const { brand } = sessionStore() const { brand } = sessionStore()
const router = useRouter() const router = useRouter()
const readOnlyMode = window.read_only_mode
onMounted(() => { onMounted(() => {
if (!user.data?.is_moderator && !user.data?.is_instructor) { if (!user.data?.is_moderator && !user.data?.is_instructor) {

View File

@@ -11,7 +11,7 @@
> >
{{ __('Generate Certificates') }} {{ __('Generate Certificates') }}
</Button> </Button>
<Button v-if="user.data?.is_moderator" @click="openAnnouncementModal()"> <Button v-if="canMakeAnnouncement()" @click="openAnnouncementModal()">
<span> <span>
{{ __('Make an Announcement') }} {{ __('Make an Announcement') }}
</span> </span>
@@ -242,6 +242,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const { brand } = sessionStore() const { brand } = sessionStore()
const tabIndex = ref(0) const tabIndex = ref(0)
const readOnlyMode = window.read_only_mode
const tabs = computed(() => { const tabs = computed(() => {
let batchTabs = [] let batchTabs = []
@@ -354,6 +355,11 @@ watch(tabIndex, () => {
} }
}) })
const canMakeAnnouncement = () => {
if (readOnlyMode) return false
return user.data?.is_moderator || user.data?.is_evaluator
}
usePageMeta(() => { usePageMeta(() => {
return { return {
title: batch?.data?.title, title: batch?.data?.title,

View File

@@ -4,7 +4,7 @@
> >
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
<router-link <router-link
v-if="user.data?.is_moderator" v-if="canCreateBatch()"
:to="{ :to="{
name: 'BatchForm', name: 'BatchForm',
params: { batchName: 'new' }, params: { batchName: 'new' },
@@ -124,6 +124,7 @@ const filters = ref({})
const is_student = computed(() => user.data?.is_student) const is_student = computed(() => user.data?.is_student)
const currentTab = ref(is_student.value ? 'All' : 'Upcoming') const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
const orderBy = ref('start_date') const orderBy = ref('start_date')
const readOnlyMode = window.read_only_mode
onMounted(() => { onMounted(() => {
setFiltersFromQuery() setFiltersFromQuery()
@@ -299,6 +300,12 @@ const batchTabs = computed(() => {
return tabs return tabs
}) })
const canCreateBatch = () => {
if (readOnlyMode) return false
if (user.data?.is_moderator || user.data?.is_instructor) return true
return false
}
const breadcrumbs = computed(() => [ const breadcrumbs = computed(() => [
{ {
label: __('Batches'), label: __('Batches'),

View File

@@ -16,7 +16,10 @@
}, },
]" ]"
/> />
<div v-if="user.data?.name" class="flex items-center space-x-2"> <div
v-if="user.data?.name && !readOnlyMode"
class="flex items-center space-x-2"
>
<router-link <router-link
v-if="user.data.name == job.data?.owner" v-if="user.data.name == job.data?.owner"
:to="{ :to="{
@@ -54,7 +57,7 @@
{{ __('You have applied') }} {{ __('You have applied') }}
</Badge> </Badge>
</div> </div>
<div v-else> <div v-else-if="!readOnlyMode">
<Button @click="redirectToLogin(job.data?.name)"> <Button @click="redirectToLogin(job.data?.name)">
<span> <span>
{{ __('Login to apply') }} {{ __('Login to apply') }}
@@ -181,6 +184,7 @@ const user = inject('$user')
const dayjs = inject('$dayjs') const dayjs = inject('$dayjs')
const { brand } = sessionStore() const { brand } = sessionStore()
const showApplicationModal = ref(false) const showApplicationModal = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
job: { job: {

View File

@@ -16,7 +16,7 @@
}, },
}" }"
> >
<Button variant="solid"> <Button v-if="!readOnlyMode" variant="solid">
<template #prefix> <template #prefix>
<Plus class="h-4 w-4" /> <Plus class="h-4 w-4" />
</template> </template>
@@ -120,6 +120,7 @@ const country = ref(null)
const filters = ref({}) const filters = ref({})
const orFilters = ref({}) const orFilters = ref({})
const jobCount = ref(0) const jobCount = ref(0)
const readOnlyMode = window.read_only_mode
onMounted(() => { onMounted(() => {
let queries = new URLSearchParams(location.search) let queries = new URLSearchParams(location.search)

View File

@@ -5,7 +5,7 @@
> >
<Breadcrumbs class="h-7" :items="breadcrumbs" /> <Breadcrumbs class="h-7" :items="breadcrumbs" />
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<Tooltip v-if="lesson.data?.membership" :text="__('Zen Mode')"> <Tooltip v-if="canGoZen()" :text="__('Zen Mode')">
<Button @click="goFullScreen()"> <Button @click="goFullScreen()">
<template #icon> <template #icon>
<Focus class="w-4 h-4 stroke-2" /> <Focus class="w-4 h-4 stroke-2" />
@@ -539,6 +539,7 @@ const checkIfDiscussionsAllowed = () => {
} }
const allowEdit = () => { const allowEdit = () => {
if (window.read_only_mode) return false
if (user.data?.is_moderator) return true if (user.data?.is_moderator) return true
if (lesson.data?.instructors?.includes(user.data?.name)) return true if (lesson.data?.instructors?.includes(user.data?.name)) return true
return false return false
@@ -574,6 +575,17 @@ const enrollStudent = () => {
) )
} }
const canGoZen = () => {
if (
user.data?.is_moderator ||
user.data?.is_instructor ||
user.data?.is_evaluator
)
return false
if (lesson.data?.membership) return true
return false
}
const goFullScreen = () => { const goFullScreen = () => {
if (lessonContainer.value.requestFullscreen) { if (lessonContainer.value.requestFullscreen) {
lessonContainer.value.requestFullscreen() lessonContainer.value.requestFullscreen()

View File

@@ -25,7 +25,11 @@
@select="(imageUrl) => coverImage.submit({ url: imageUrl })" @select="(imageUrl) => coverImage.submit({ url: imageUrl })"
> >
<template v-slot="{ togglePopover }"> <template v-slot="{ togglePopover }">
<Button variant="outline" @click="togglePopover()"> <Button
v-if="!readOnlyMode"
variant="outline"
@click="togglePopover()"
>
<template #prefix> <template #prefix>
<Edit class="w-4 h-4 stroke-1.5 text-ink-gray-7" /> <Edit class="w-4 h-4 stroke-1.5 text-ink-gray-7" />
</template> </template>
@@ -58,7 +62,7 @@
</div> </div>
</div> </div>
<Button <Button
v-if="isSessionUser()" v-if="isSessionUser() && !readOnlyMode"
class="mt-3 sm:mt-0 md:ml-auto" class="mt-3 sm:mt-0 md:ml-auto"
@click="editProfile()" @click="editProfile()"
> >
@@ -95,7 +99,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { computed, inject, watch, ref, onMounted, watchEffect } from 'vue' import { computed, inject, watch, ref, onMounted, watchEffect } from 'vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { Edit, icons } from 'lucide-vue-next' import { Edit } from 'lucide-vue-next'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import NoPermission from '@/components/NoPermission.vue' import NoPermission from '@/components/NoPermission.vue'
@@ -109,6 +113,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const activeTab = ref('') const activeTab = ref('')
const showProfileModal = ref(false) const showProfileModal = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
username: { username: {

View File

@@ -4,7 +4,21 @@
{{ __('My availability') }} {{ __('My availability') }}
</h2> </h2>
<div class=""> <div
v-if="readOnlyMode"
class="flex items-center space-x-2 text-sm text-ink-gray-7 bg-surface-gray-1 px-3 py-2 rounded-md w-full text-center"
>
<CircleAlert class="size-4 stroke-1.5" />
<span>
{{
__(
'You cannot change the availability when the site is being updated.'
)
}}
</span>
</div>
<div v-else>
<div>
<div <div
class="grid grid-cols-3 md:grid-cols-4 gap-4 text-sm text-ink-gray-7 mb-4" class="grid grid-cols-3 md:grid-cols-4 gap-4 text-sm text-ink-gray-7 mb-4"
> >
@@ -124,14 +138,16 @@
</Button> </Button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { createResource, FormControl, Button } from 'frappe-ui' import { createResource, FormControl, Button, Badge } from 'frappe-ui'
import { computed, reactive, ref, onMounted, inject } from 'vue' import { computed, reactive, ref, onMounted, inject } from 'vue'
import { showToast, convertToTitleCase } from '@/utils' import { showToast, convertToTitleCase } from '@/utils'
import { Plus, X, Check } from 'lucide-vue-next' import { Plus, X, Check, CircleAlert } from 'lucide-vue-next'
const user = inject('$user') const user = inject('$user')
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
profile: { profile: {

View File

@@ -4,6 +4,16 @@
{{ __('Settings') }} {{ __('Settings') }}
</h2> </h2>
<div <div
v-if="readOnlyMode"
class="flex items-center space-x-2 text-sm text-ink-gray-7 bg-surface-gray-1 px-3 py-2 rounded-md w-full text-center"
>
<CircleAlert class="size-4 stroke-1.5" />
<span>
{{ __('You cannot change the roles in read-only mode.') }}
</span>
</div>
<div
v-else
class="flex flex-col md:flex-row gap-4 md:gap-0 justify-between w-3/4 mt-5" class="flex flex-col md:flex-row gap-4 md:gap-0 justify-between w-3/4 mt-5"
> >
<FormControl <FormControl
@@ -37,11 +47,13 @@
import { FormControl, createResource } from 'frappe-ui' import { FormControl, createResource } from 'frappe-ui'
import { ref } from 'vue' import { ref } from 'vue'
import { showToast, convertToTitleCase } from '@/utils' import { showToast, convertToTitleCase } from '@/utils'
import { CircleAlert } from 'lucide-vue-next'
const moderator = ref(false) const moderator = ref(false)
const course_creator = ref(false) const course_creator = ref(false)
const batch_evaluator = ref(false) const batch_evaluator = ref(false)
const lms_student = ref(false) const lms_student = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
profile: { profile: {

View File

@@ -4,7 +4,7 @@
> >
<Breadcrumbs :items="breadbrumbs" /> <Breadcrumbs :items="breadbrumbs" />
<Button <Button
v-if="user.data?.is_moderator || user.data?.is_instructor" v-if="canCreateProgram()"
@click="showDialog = true" @click="showDialog = true"
variant="solid" variant="solid"
> >
@@ -46,7 +46,7 @@
params: { programName: program.name }, params: { programName: program.name },
}" }"
> >
<Button> <Button v-if="!readOnlyMode">
<template #prefix> <template #prefix>
<Edit class="h-4 w-4 stroke-1.5" /> <Edit class="h-4 w-4 stroke-1.5" />
</template> </template>
@@ -142,6 +142,7 @@ const showDialog = ref(false)
const router = useRouter() const router = useRouter()
const title = ref('') const title = ref('')
const settings = useSettings() const settings = useSettings()
const readOnlyMode = window.read_only_mode
onMounted(() => { onMounted(() => {
if ( if (
@@ -208,6 +209,12 @@ const lockCourse = (course) => {
return true return true
} }
const canCreateProgram = () => {
if (readOnlyMode) return false
if (user.data?.is_moderator || user.data?.is_instructor) return true
return false
}
const breadbrumbs = computed(() => [ const breadbrumbs = computed(() => [
{ {
label: 'Programs', label: 'Programs',

View File

@@ -3,7 +3,7 @@
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5" class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
> >
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
<div class="space-x-2"> <div v-if="!readOnlyMode" class="space-x-2">
<router-link <router-link
v-if="quizDetails.data?.name" v-if="quizDetails.data?.name"
:to="{ :to="{
@@ -116,7 +116,7 @@
<div class="font-semibold"> <div class="font-semibold">
{{ __('Questions') }} {{ __('Questions') }}
</div> </div>
<Button @click="openQuestionModal()"> <Button v-if="!readOnlyMode" @click="openQuestionModal()">
<template #prefix> <template #prefix>
<Plus class="w-4 h-4" /> <Plus class="w-4 h-4" />
</template> </template>
@@ -223,6 +223,7 @@ const currentQuestion = reactive({
}) })
const user = inject('$user') const user = inject('$user')
const router = useRouter() const router = useRouter()
const readOnlyMode = window.read_only_mode
const props = defineProps({ const props = defineProps({
quizID: { quizID: {

View File

@@ -4,6 +4,7 @@
> >
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
<router-link <router-link
v-if="!readOnlyMode"
:to="{ :to="{
name: 'QuizForm', name: 'QuizForm',
params: { params: {
@@ -89,6 +90,7 @@ import { sessionStore } from '@/stores/session'
const { brand } = sessionStore() const { brand } = sessionStore()
const user = inject('$user') const user = inject('$user')
const router = useRouter() const router = useRouter()
const readOnlyMode = window.read_only_mode
onMounted(() => { onMounted(() => {
if (!user.data?.is_moderator && !user.data?.is_instructor) { if (!user.data?.is_moderator && !user.data?.is_instructor) {

View File

@@ -8,6 +8,10 @@ no_cache = 1
def get_context(): def get_context():
context = frappe._dict()
context.boot = get_boot()
frappe.db.commit()
app_path = frappe.form_dict.get("app_path") app_path = frappe.form_dict.get("app_path")
favicon = ( favicon = (
frappe.db.get_single_value("Website Settings", "favicon") frappe.db.get_single_value("Website Settings", "favicon")
@@ -15,19 +19,24 @@ def get_context():
) )
title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning" title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning"
csrf_token = frappe.sessions.get_csrf_token()
frappe.db.commit()
context = frappe._dict()
context.csrf_token = csrf_token
context.read_only = frappe.flags.read_only
context.meta = get_meta(app_path, title, favicon) context.meta = get_meta(app_path, title, favicon)
capture("active_site", "lms")
context.title = title context.title = title
context.favicon = favicon context.favicon = favicon
capture("active_site", "lms")
return context return context
def get_boot():
return frappe._dict(
{
"frappe_version": frappe.__version__,
"read_only_mode": frappe.flags.read_only,
"csrf_token": frappe.sessions.get_csrf_token(),
}
)
def get_meta(app_path, title, favicon): def get_meta(app_path, title, favicon):
meta = frappe._dict() meta = frappe._dict()
if app_path: if app_path: