feat: restrict certain UI elements for students in course sidebar and lesson pages
This commit is contained in:
+14
-12
@@ -253,18 +253,20 @@
|
||||
<span>{item.title}</span>
|
||||
|
||||
<div class="ml-auto flex items-center gap-1">
|
||||
<IconButton
|
||||
variant="ghost-outline"
|
||||
size="icon-xs"
|
||||
class="transition-opacity duration-150 {isHovered ? 'opacity-100' : 'opacity-0'}"
|
||||
onclick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
openContentModal(id);
|
||||
}}
|
||||
>
|
||||
<Plus size={8} />
|
||||
</IconButton>
|
||||
{#if !isStudent}
|
||||
<IconButton
|
||||
variant="ghost-outline"
|
||||
size="icon-xs"
|
||||
class="transition-opacity duration-150 {isHovered ? 'opacity-100' : 'opacity-0'}"
|
||||
onclick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
openContentModal(id);
|
||||
}}
|
||||
>
|
||||
<Plus size={8} />
|
||||
</IconButton>
|
||||
{/if}
|
||||
{#if showContentCount}
|
||||
<ContentCountBadges lessons={contentCount.lessons} exercises={contentCount.exercises} />
|
||||
{/if}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import { resolve } from '$app/paths';
|
||||
import { untrack } from 'svelte';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
@@ -65,18 +66,16 @@
|
||||
|
||||
const lessonTitle = $derived(lessonApi.lesson?.title || 'Lesson');
|
||||
const isLessonUnlocked = $derived(lessonApi.lesson?.isUnlocked ?? false);
|
||||
$inspect(isLessonUnlocked);
|
||||
$inspect($globalStore.isStudent);
|
||||
|
||||
function setModeQueryParam(value: (typeof MODES)[keyof typeof MODES]) {
|
||||
const params = new URLSearchParams($page.url.searchParams);
|
||||
const params = new SvelteURLSearchParams($page.url.searchParams);
|
||||
params.set('mode', value);
|
||||
goto(`${$page.url.pathname}?${params.toString()}`, { replaceState: false });
|
||||
goto(resolve(`${$page.url.pathname}?${params.toString()}`, {}), { replaceState: false });
|
||||
}
|
||||
function setTabQueryParam(value: string) {
|
||||
const params = new URLSearchParams($page.url.searchParams);
|
||||
const params = new SvelteURLSearchParams($page.url.searchParams);
|
||||
params.set('tab', value);
|
||||
goto(`${$page.url.pathname}?${params.toString()}`, { replaceState: false });
|
||||
goto(resolve(`${$page.url.pathname}?${params.toString()}`, {}), { replaceState: false });
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
@@ -117,7 +116,7 @@
|
||||
if (tab.value === 1) {
|
||||
tab.badgeValue = isHtmlValueEmpty(content) ? 0 : 1;
|
||||
} else if (tab.value === 2) {
|
||||
tab.badgeValue = !!slideUrl ? 1 : 0;
|
||||
tab.badgeValue = slideUrl ? 1 : 0;
|
||||
} else if (tab.value === 3) {
|
||||
tab.badgeValue = !isEmpty(videos) ? videos.length : 0;
|
||||
} else if (tab.value === 4) {
|
||||
@@ -372,7 +371,7 @@
|
||||
>
|
||||
<!-- Tabs List -->
|
||||
<UnderlineTabs.List>
|
||||
{#each tabs as tab}
|
||||
{#each tabs as tab (tab.value)}
|
||||
<UnderlineTabs.Trigger value={String(tab.value)}>
|
||||
{#if tab.icon}
|
||||
<tab.icon size={16} />
|
||||
@@ -413,7 +412,7 @@
|
||||
{:else if lessonApi.lesson && !isMaterialsEmpty}
|
||||
{#key lessonId}
|
||||
<div class="mb-20 flex w-full flex-col gap-2" in:fade={{ delay: 500 }} out:fade>
|
||||
{#each viewModeComponents as Component}
|
||||
{#each viewModeComponents as Component, index (index)}
|
||||
<Component {mode} {lessonId} />
|
||||
{/each}
|
||||
|
||||
@@ -437,9 +436,11 @@
|
||||
variant="page"
|
||||
class="text-center"
|
||||
>
|
||||
<Button onclick={toggleMode}>
|
||||
{$t('course.navItem.lessons.materials.get_started')}
|
||||
</Button>
|
||||
{#if !$globalStore.isStudent}
|
||||
<Button onclick={toggleMode}>
|
||||
{$t('course.navItem.lessons.materials.get_started')}
|
||||
</Button>
|
||||
{/if}
|
||||
</Empty>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { derived, writable } from 'svelte/store';
|
||||
|
||||
import { PUBLIC_IS_SELFHOSTED } from '$env/static/public';
|
||||
import { currentOrgPath } from './org';
|
||||
|
||||
@@ -17,6 +18,12 @@ export const globalStore = writable<{
|
||||
/**
|
||||
* Single source of truth: is this a student experience?
|
||||
* Cloud: orgSite is always student. Self-hosted: check role.
|
||||
*
|
||||
* why are we not using isOrgStudent from org store?
|
||||
* because this is derived from the currentOrg which returns null if no org OR boolean if org is present. Once org data is set, we then update globalStore.isStudent to the same value.
|
||||
*
|
||||
* why are we not using only globalStore.isStudent for this?
|
||||
* because in the cloud instance, when a teacher logs into the lms aka org site, they need to be treated as a student so they see what the student sees.
|
||||
*/
|
||||
export const isStudentExperience = derived(globalStore, ($gs) => {
|
||||
const isCloud = PUBLIC_IS_SELFHOSTED !== 'true';
|
||||
|
||||
@@ -84,7 +84,8 @@
|
||||
already_joined: result.data.alreadyJoined
|
||||
});
|
||||
|
||||
goto(result.data.redirectTo || '/lms');
|
||||
// need to force page reload to avoid cache issues
|
||||
window.location.href = result.data.redirectTo || '/lms';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
</script>
|
||||
|
||||
<Page.Root class="mx-auto flex w-[calc(100vw-var(--sidebar-width))]!">
|
||||
<Page.Root class="mx-auto flex w-[calc(95vw-var(--sidebar-width))]!">
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
<Page.Title>
|
||||
|
||||
Reference in New Issue
Block a user