chore: resolved conflicts

This commit is contained in:
Jannat Patel
2026-02-24 12:05:44 +05:30
16 changed files with 182 additions and 198 deletions

View File

@@ -93,11 +93,19 @@
</div>
</template>
<script setup>
import { createResource, TextEditor, Button, Dropdown, toast } from 'frappe-ui'
import {
call,
createResource,
TextEditor,
Button,
Dropdown,
toast,
} from 'frappe-ui'
import { timeAgo } from '@/utils'
import UserAvatar from '@/components/UserAvatar.vue'
import { ChevronLeft, MoreHorizontal } from 'lucide-vue-next'
import { ref, inject, onMounted, onUnmounted } from 'vue'
import { useTelemetry } from 'frappe-ui/frappe'
const showTopics = defineModel('showTopics')
const newReply = ref('')
@@ -107,6 +115,7 @@ const allUsers = inject('$allUsers')
const mentionUsers = ref([])
const renderEditor = ref(false)
const readOnlyMode = window.read_only_mode
const { capture } = useTelemetry()
const props = defineProps({
topic: {
@@ -143,19 +152,6 @@ const replies = createResource({
auto: true,
})
const newReplyResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Reply',
reply: newReply.value,
topic: props.topic.name,
},
}
},
})
const fetchMentionUsers = () => {
if (user.data?.is_student) {
renderEditor.value = true
@@ -178,78 +174,61 @@ const fetchMentionUsers = () => {
}
const postReply = () => {
newReplyResource.submit(
{},
{
validate() {
if (!newReply.value) {
return 'Reply cannot be empty'
}
},
onSuccess() {
newReply.value = ''
replies.reload()
},
onError(err) {
toast.error(err.messages?.[0] || err)
},
}
)
}
const editReplyResource = createResource({
url: 'frappe.client.set_value',
makeParams(values) {
return {
if (!newReply.value) {
toast.error(__('Reply cannot be empty.'))
return
}
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Reply',
name: values.name,
fieldname: 'reply',
value: values.reply,
}
},
})
reply: newReply.value,
topic: props.topic.name,
},
})
.then((data) => {
newReply.value = ''
replies.reload()
capture('discussion_reply_created')
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const postEdited = (reply) => {
editReplyResource.submit(
{
name: reply.name,
reply: reply.reply,
},
{
validate() {
if (!reply.reply) {
return 'Reply cannot be empty'
}
},
onSuccess() {
reply.editable = false
replies.reload()
},
}
)
if (!reply.reply) {
toast.error(__('Reply cannot be empty.'))
return
}
call('frappe.client.set_value', {
doctype: 'Discussion Reply',
name: reply.name,
fieldname: 'reply',
value: reply.reply,
})
.then(() => {
reply.editable = false
replies.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const deleteReplyResource = createResource({
url: 'frappe.client.delete',
makeParams(values) {
return {
doctype: 'Discussion Reply',
name: values.name,
}
},
})
const deleteReply = (reply) => {
deleteReplyResource.submit(
{
name: reply.name,
},
{
onSuccess() {
replies.reload()
},
}
)
call('frappe.client.delete', {
doctype: 'Discussion Reply',
name: reply.name,
})
.then(() => {
replies.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
onUnmounted(() => {

View File

@@ -34,17 +34,13 @@
</Dialog>
</template>
<script setup>
import {
Dialog,
FormControl,
TextEditor,
createResource,
toast,
} from 'frappe-ui'
import { call, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { reactive } from 'vue'
import { singularize } from '@/utils'
import { useTelemetry } from 'frappe-ui/frappe'
const topics = defineModel('reloadTopics')
const { capture } = useTelemetry()
const props = defineProps({
title: {
@@ -66,64 +62,50 @@ const topic = reactive({
reply: '',
})
const topicResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Topic',
reference_doctype: props.doctype,
reference_docname: props.docname,
title: topic.title,
},
}
},
})
const replyResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Reply',
topic: values.topic,
reply: topic.reply,
},
}
},
})
const submitTopic = (close) => {
topicResource.submit(
{},
{
validate() {
if (!topic.title) {
return 'Title cannot be empty.'
}
if (!topic.reply) {
return 'Reply cannot be empty.'
}
},
onSuccess(data) {
replyResource.submit(
{
topic: data.name,
},
{
onSuccess() {
topic.title = ''
topic.reply = ''
topics.value.reload()
close()
},
}
)
},
onError(err) {
toast.error(err.messages?.[0] || err)
},
}
)
if (!topic.title) {
toast.error(__('Title cannot be empty.'))
return
}
if (!topic.reply) {
toast.error(__('Details cannot be empty.'))
return
}
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Topic',
reference_doctype: props.doctype,
reference_docname: props.docname,
title: topic.title,
},
})
.then((data) => {
createReply(data.name, close)
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const createReply = (topicName, close) => {
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Reply',
topic: topicName,
reply: topic.reply,
},
})
.then((data) => {
topic.title = ''
topic.reply = ''
topics.value.reload()
capture('discussion_topic_created')
close()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
</script>

View File

@@ -131,6 +131,7 @@ import { ref, watch, reactive, inject } from 'vue'
import { RefreshCw, Plus, Search, Shield } from 'lucide-vue-next'
import { useOnboarding } from 'frappe-ui/frappe'
import type { User } from '@/components/Settings/types'
import { useTelemetry } from 'frappe-ui/frappe'
type Member = {
username: string
@@ -149,6 +150,7 @@ const hasNextPage = ref(false)
const showForm = ref(false)
const user = inject<User | null>('$user')
const { updateOnboardingStep } = useOnboarding('learning')
const { capture } = useTelemetry()
const member = reactive({
email: '',
@@ -202,6 +204,7 @@ const addMember = (close: () => void) => {
})
.then((data: Member) => {
if (user?.data?.is_system_manager) updateOnboardingStep('invite_students')
capture('user_added')
show.value = false
router.push({
name: 'ProfileRoles',

View File

@@ -111,7 +111,7 @@
<script setup lang="ts">
import { Button, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
import { ref, inject, onMounted, onBeforeUnmount, watch } from 'vue'
import { ref, inject, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { cleanError, openSettings } from '@/utils'
import Link from '@/components/Controls/Link.vue'
@@ -188,13 +188,13 @@ const keyboardShortcut = (e: KeyboardEvent) => {
onMounted(() => {
window.addEventListener('keydown', keyboardShortcut)
capture('batch_form_opened')
})
onBeforeUnmount(() => {
window.removeEventListener('keydown', keyboardShortcut)
})
watch(show, () => {
if (show.value) capture('batch_form_opened')
capture('batch_form_closed', {
data: batch.value,
})
})
</script>

View File

@@ -75,7 +75,7 @@
<script setup lang="ts">
import { Button, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
import { inject, onMounted, onBeforeUnmount, ref, watch } from 'vue'
import { inject, onMounted, onBeforeUnmount, ref } from 'vue'
import { useRouter } from 'vue-router'
import { cleanError, openSettings } from '@/utils'
import Link from '@/components/Controls/Link.vue'
@@ -148,13 +148,13 @@ const keyboardShortcut = (e: KeyboardEvent) => {
onMounted(() => {
window.addEventListener('keydown', keyboardShortcut)
capture('course_form_opened')
})
onBeforeUnmount(() => {
window.removeEventListener('keydown', keyboardShortcut)
})
watch(show, () => {
if (show.value) capture('course_form_opened')
capture('course_form_closed', {
data: course.value,
})
})
</script>

View File

@@ -1,9 +1,4 @@
<template>
<!-- <header
class="sticky flex items-center justify-between top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs :items="[{ label: __('Home'), route: { name: 'Home' } }]" />
</header> -->
<div class="w-full px-5 pt-5 pb-10">
<div class="space-y-2">
<div class="flex items-center justify-between">
@@ -11,9 +6,8 @@
{{ __('Hey') }}, {{ user.data?.full_name }} 👋
</div>
<div>
<TabButtons v-if="isAdmin" v-model="currentTab" :buttons="tabs" />
<div
v-else
v-if="!isAdmin"
@click="showStreakModal = true"
class="bg-surface-amber-2 px-2 py-1 rounded-md cursor-pointer"
>
@@ -35,19 +29,16 @@
:liveClasses="adminLiveClasses"
:evals="adminEvals"
/>
<StudentHome v-else :myLiveClasses="myLiveClasses" />
<StudentHome
v-else-if="currentTab === 'student'"
:myLiveClasses="myLiveClasses"
/>
</div>
<Streak v-model="showStreakModal" :streakInfo="streakInfo" />
</template>
<script setup lang="ts">
import { computed, inject, onMounted, ref } from 'vue'
import {
Breadcrumbs,
call,
createResource,
TabButtons,
usePageMeta,
} from 'frappe-ui'
import { call, createResource, usePageMeta } from 'frappe-ui'
import { sessionStore } from '@/stores/session'
import StudentHome from '@/pages/Home/StudentHome.vue'
import AdminHome from '@/pages/Home/AdminHome.vue'
@@ -56,10 +47,10 @@ import Streak from '@/pages/Home/Streak.vue'
const user = inject<any>('$user')
const { brand } = sessionStore()
const evalCount = ref(0)
const currentTab = ref<'student' | 'instructor'>('instructor')
const currentTab = ref<'student' | 'instructor'>('student')
const showStreakModal = ref(false)
onMounted(() => {
const fetchEvalCount = () => {
call('frappe.client.get_count', {
doctype: 'LMS Certificate Request',
filters: {
@@ -70,7 +61,7 @@ onMounted(() => {
}).then((data: any) => {
evalCount.value = data
})
})
}
const isAdmin = computed(() => {
return (
@@ -80,6 +71,15 @@ const isAdmin = computed(() => {
)
})
onMounted(() => {
if (isAdmin.value) {
currentTab.value = 'instructor'
} else {
currentTab.value = 'student'
fetchEvalCount()
}
})
const myLiveClasses = createResource({
url: 'lms.lms.api.get_my_live_classes',
auto: !isAdmin.value ? true : false,
@@ -151,11 +151,6 @@ const subtitle = computed(() => {
}
})
const tabs = [
{ label: __('Student'), value: 'student' },
{ label: __('Instructor'), value: 'instructor' },
]
usePageMeta(() => {
return {
title: __('Home'),

View File

@@ -783,11 +783,11 @@ const isAdmin = computed(() => {
const allowEdit = () => {
if (window.read_only_mode) return false
if (isAdmin.value) return true
return false
return isAdmin.value
}
const allowInstructorContent = () => {
if (window.read_only_mode) return false
return isAdmin.value
}

View File

@@ -236,10 +236,10 @@ export function getEditorTools() {
html: "<iframe style='width: 100%; height: 30rem; border: 1px solid #D3D3D3; border-radius: 12px; margin: 1rem 0;' frameborder='0' allowfullscreen='true'></iframe>",
},
codesandbox: {
regex: /^https:\/\/codesandbox\.io\/(?:embed\/)?([A-Za-z0-9_-]+)(?:\?[^\/]*)?$/,
regex: /^https:\/\/codesandbox\.io\/(?:(?:p\/(?:sandbox|devbox)\/)|(?:embed\/)|(?:s\/))?([A-Za-z0-9_-]+)(?:[\/\?].*)?$/,
embedUrl:
'https://codesandbox.io/embed/<%= remote_id %>?view=editor+%2B+preview&module=%2Findex.html',
html: "<iframe style='width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;' sandbox='allow-mods allow-forms allow-popups allow-scripts allow-same-origin' frameborder='0' allowfullscreen='true'></iframe>",
html: "<iframe style='width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;' sandbox='allow-modals allow-forms allow-popups allow-scripts allow-same-origin' frameborder='0' allowfullscreen='true'></iframe>",
},
},
},