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

@@ -39,7 +39,11 @@
{{ __('More') }}
</span>
</div>
<Button v-if="isModerator" variant="ghost" @click="openPageModal()">
<Button
v-if="isModerator && !readOnlyMode"
variant="ghost"
@click="openPageModal()"
>
<template #icon>
<Plus class="h-4 w-4 text-ink-gray-7 stroke-1.5" />
</template>
@@ -63,6 +67,16 @@
</div>
</div>
<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
v-if="
userResource.data?.is_system_manager && userResource.data?.is_fc_site
@@ -89,15 +103,31 @@
: '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')">
<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()"
/>
</Tooltip>
<Tooltip :text="__('Help')">
<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="
() => {
showHelpModal = minimize ? true : !showHelpModal
@@ -113,7 +143,7 @@
"
>
<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="{
'[transform:rotateY(180deg)]': sidebarStore.isSidebarCollapsed,
}"
@@ -166,6 +196,7 @@ import { useRouter } from 'vue-router'
import InviteIcon from './Icons/InviteIcon.vue'
import {
BookOpen,
CircleAlert,
ChevronRight,
Plus,
CircleHelp,
@@ -203,6 +234,7 @@ const currentStep = ref({})
const router = useRouter()
let onboardingDetails
let isOnboardingStepsCompleted = false
const readOnlyMode = window.read_only_mode
const iconProps = {
strokeWidth: 1.5,
width: 16,

View File

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

View File

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

View File

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

View File

@@ -49,68 +49,70 @@
{{ batch.data.timezone }}
</span>
</div>
<router-link
v-if="isModerator || isStudent"
:to="{
name: 'Batch',
params: {
batchName: batch.data.name,
},
}"
>
<Button variant="solid" class="w-full mt-4">
<span>
{{ isModerator ? __('Manage Batch') : __('Visit Batch') }}
</span>
<div v-if="!readOnlyMode">
<router-link
v-if="isModerator || isStudent"
:to="{
name: 'Batch',
params: {
batchName: batch.data.name,
},
}"
>
<Button variant="solid" class="w-full mt-4">
<span>
{{ isModerator ? __('Manage Batch') : __('Visit Batch') }}
</span>
</Button>
</router-link>
<router-link
:to="{
name: 'Billing',
params: {
type: 'batch',
name: batch.data.name,
},
}"
v-else-if="
batch.data.paid_batch &&
batch.data.seats_left > 0 &&
batch.data.accept_enrollments
"
>
<Button v-if="!isStudent" class="w-full mt-4" variant="solid">
<span>
{{ __('Register Now') }}
</span>
</Button>
</router-link>
<Button
variant="solid"
class="w-full mt-2"
v-else-if="
batch.data.allow_self_enrollment &&
batch.data.seats_left &&
batch.data.accept_enrollments
"
@click="enrollInBatch()"
>
{{ __('Enroll Now') }}
</Button>
</router-link>
<router-link
:to="{
name: 'Billing',
params: {
type: 'batch',
name: batch.data.name,
},
}"
v-else-if="
batch.data.paid_batch &&
batch.data.seats_left > 0 &&
batch.data.accept_enrollments
"
>
<Button v-if="!isStudent" class="w-full mt-4" variant="solid">
<span>
{{ __('Register Now') }}
</span>
</Button>
</router-link>
<Button
variant="solid"
class="w-full mt-2"
v-else-if="
batch.data.allow_self_enrollment &&
batch.data.seats_left &&
batch.data.accept_enrollments
"
@click="enrollInBatch()"
>
{{ __('Enroll Now') }}
</Button>
<router-link
v-if="isModerator"
:to="{
name: 'BatchForm',
params: {
batchName: batch.data.name,
},
}"
>
<Button class="w-full mt-2">
<span>
{{ __('Edit') }}
</span>
</Button>
</router-link>
<router-link
v-if="isModerator"
:to="{
name: 'BatchForm',
params: {
batchName: batch.data.name,
},
}"
>
<Button class="w-full mt-2">
<span>
{{ __('Edit') }}
</span>
</Button>
</router-link>
</div>
</div>
</template>
<script setup>
@@ -123,7 +125,7 @@ import { useRouter } from 'vue-router'
const router = useRouter()
const user = inject('$user')
const dayjs = inject('$dayjs')
const readOnlyMode = window.read_only_mode
const props = defineProps({
batch: {

View File

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

View File

@@ -9,89 +9,94 @@
<div v-if="course.data.paid_course" class="text-2xl font-semibold mb-3">
{{ course.data.price }}
</div>
<div v-if="course.data.membership" class="space-y-2">
<div v-if="!readOnlyMode">
<div v-if="course.data.membership" class="space-y-2">
<router-link
:to="{
name: 'Lesson',
params: {
courseName: course.name,
chapterNumber: course.data.current_lesson
? course.data.current_lesson.split('-')[0]
: 1,
lessonNumber: course.data.current_lesson
? course.data.current_lesson.split('-')[1]
: 1,
},
}"
>
<Button variant="solid" size="md" class="w-full">
<span>
{{ __('Continue Learning') }}
</span>
</Button>
</router-link>
<CertificationLinks :courseName="course.data.name" class="w-full" />
</div>
<router-link
v-else-if="course.data.paid_course"
:to="{
name: 'Lesson',
name: 'Billing',
params: {
courseName: course.name,
chapterNumber: course.data.current_lesson
? course.data.current_lesson.split('-')[0]
: 1,
lessonNumber: course.data.current_lesson
? course.data.current_lesson.split('-')[1]
: 1,
type: 'course',
name: course.data.name,
},
}"
>
<Button variant="solid" size="md" class="w-full">
<span>
{{ __('Continue Learning') }}
{{ __('Buy this course') }}
</span>
</Button>
</router-link>
<Badge
v-else-if="course.data.disable_self_learning"
theme="blue"
size="lg"
>
{{ __('Contact the Administrator to enroll for this course.') }}
</Badge>
<Button
v-else
@click="enrollStudent()"
variant="solid"
class="w-full"
size="md"
>
<span>
{{ __('Start Learning') }}
</span>
</Button>
<Button
v-if="canGetCertificate"
@click="fetchCertificate()"
variant="subtle"
class="w-full mt-2"
size="md"
>
{{ __('Get Certificate') }}
</Button>
<router-link
v-if="user?.data?.is_moderator || is_instructor()"
:to="{
name: 'CourseForm',
params: {
courseName: course.data.name,
},
}"
>
<Button variant="subtle" class="w-full mt-2" size="md">
<span>
{{ __('Edit') }}
</span>
</Button>
</router-link>
<CertificationLinks :courseName="course.data.name" class="w-full" />
</div>
<router-link
v-else-if="course.data.paid_course"
:to="{
name: 'Billing',
params: {
type: 'course',
name: course.data.name,
},
}"
>
<Button variant="solid" size="md" class="w-full">
<span>
{{ __('Buy this course') }}
</span>
</Button>
</router-link>
<Badge
v-else-if="course.data.disable_self_learning"
theme="blue"
size="lg"
>
{{ __('Contact the Administrator to enroll for this course.') }}
</Badge>
<Button
v-else
@click="enrollStudent()"
variant="solid"
class="w-full"
size="md"
>
<span>
{{ __('Start Learning') }}
</span>
</Button>
<Button
v-if="canGetCertificate"
@click="fetchCertificate()"
variant="subtle"
class="w-full mt-2"
size="md"
>
{{ __('Get Certificate') }}
</Button>
<router-link
v-if="user?.data?.is_moderator || is_instructor()"
:to="{
name: 'CourseForm',
params: {
courseName: course.data.name,
},
}"
>
<Button variant="subtle" class="w-full mt-2" size="md">
<span>
{{ __('Edit') }}
</span>
</Button>
</router-link>
<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:') }}
</div>
<div class="flex items-center text-ink-gray-9">
@@ -149,6 +154,7 @@ import CertificationLinks from '@/components/CertificationLinks.vue'
const router = useRouter()
const user = inject('$user')
const readOnlyMode = window.read_only_mode
const props = defineProps({
course: {

View File

@@ -8,18 +8,6 @@
<AppSidebar />
</div>
<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 />
</div>
</div>
@@ -28,6 +16,4 @@
</template>
<script setup>
import AppSidebar from './AppSidebar.vue'
const readOnlyMode = window.read_only_mode
</script>

View File

@@ -27,7 +27,9 @@
</span>
</div>
<Dropdown
v-if="user.data.name == reply.owner && !reply.editable"
v-if="
user.data.name == reply.owner && !reply.editable && !readOnlyMode
"
:options="[
{
label: 'Edit',
@@ -71,7 +73,7 @@
</div>
<TextEditor
v-if="renderEditor"
v-if="renderEditor && !readOnlyMode"
class="mt-5"
:content="newReply"
:mentions="mentionUsers"
@@ -80,7 +82,7 @@
: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"
/>
<div class="flex justify-between mt-2">
<div v-if="!readOnlyMode" class="flex justify-between mt-2">
<span> </span>
<Button @click="postReply()">
<span>
@@ -105,6 +107,7 @@ const user = inject('$user')
const allUsers = inject('$allUsers')
const mentionUsers = ref([])
const renderEditor = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({
topic: {

View File

@@ -1,6 +1,10 @@
<template>
<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)) }}
</Button>
<div class="text-xl font-semibold text-ink-gray-9">
@@ -77,6 +81,7 @@ const currentTopic = ref(null)
const socket = inject('$socket')
const user = inject('$user')
const showTopicModal = ref(false)
const readOnlyMode = window.read_only_mode
const props = defineProps({
title: {

View File

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