feat: customize the LMS from dashboard (#304)

* Done with the customization of the LMS task

* generated custom_lms migration file

* updated the migration file

* copy improvements

* update wording

* made updates based on reviews,i hope i didnt miss anything

* updated the translations and migration files

* refactoring

* disabled improvements for apps

* more improvements

* more custom improvements

* pr refactoring

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Em_Dev
2024-05-17 01:28:59 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 78db68fa51
commit 73eb0ad4b1
30 changed files with 1013 additions and 200 deletions
@@ -17,6 +17,8 @@
import { browser } from '$app/environment';
import { course } from '$lib/components/Course/store';
import { lesson } from '$lib/components/Course/components/Lesson/store/lessons';
import { currentOrg } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
let appBarRef: HTMLDivElement;
let appContentRef: HTMLDivElement;
@@ -80,10 +82,18 @@
<div class="mb-2 relative {getAppClass(APPS_CONSTANTS.APPS.COMMENTS, $apps.selectedApp)}">
<IconButton
toolTipProps={{ title: 'Comments', hotkeys: ['Ctrl/Command', '1'] }}
toolTipProps={{
title: `${
$globalStore.isOrgSite && !$currentOrg.customization.apps.comments
? $t('course.navItem.lessons.disabled')
: $t('course.navItem.lessons.comments.title')
}`,
hotkeys: ['Ctrl/Command', '1']
}}
value={APPS_CONSTANTS.APPS.COMMENTS}
onClick={handleAppClick}
buttonClassName="relative"
disabled={$globalStore.isOrgSite && !$currentOrg.customization.apps.comments}
>
<Chip
value={$lesson.totalComments}
@@ -92,11 +102,20 @@
<SendAlt size={24} class="carbon-icon dark:text-white" />
</IconButton>
</div>
<div class="mb-2 {getAppClass(APPS_CONSTANTS.APPS.POLL, $apps.selectedApp)}">
<IconButton
toolTipProps={{ title: 'Poll', hotkeys: ['Ctrl/Command', '2'] }}
toolTipProps={{
title: `${
$globalStore.isOrgSite && !$currentOrg.customization.apps.poll
? $t('course.navItem.lessons.disabled')
: $t('course.navItem.lessons.polls.title')
} `,
hotkeys: ['Ctrl/Command', '2']
}}
value={APPS_CONSTANTS.APPS.POLL}
onClick={handleAppClick}
disabled={$globalStore.isOrgSite && !$currentOrg.customization.apps.poll}
>
<Chip
value={$course.polls.length}
@@ -105,6 +124,7 @@
<ChartPie size={24} class="carbon-icon dark:text-white" />
</IconButton>
</div>
<!-- <div class="mb-2">
<IconButton
toolTipProps={{ title: 'QandA', hotkeys: ['A', '3'] }}
@@ -15,7 +15,7 @@
import { sideBar } from '$lib/components/Org/store';
import { profile } from '$lib/utils/store/user';
import { getIsLessonComplete } from '../Lesson/functions';
import { isFreePlan } from '$lib/utils/store/org';
import { currentOrg, isFreePlan } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
export let path: string;
@@ -141,7 +141,10 @@
label: $t('course.navItems.nav_news_feed'),
to: getNavItemRoute($course.id),
hideSortIcon: true,
isPaidFeature: false
isPaidFeature: false,
show() {
return isStudent ? $currentOrg.customization.course.newsfeed : true;
}
},
{
label: $t('course.navItems.nav_lessons'),
@@ -170,7 +173,10 @@
label: $t('course.navItems.nav_marks'),
to: getNavItemRoute($course.id, 'marks'),
isPaidFeature: false,
hideSortIcon: true
hideSortIcon: true,
show() {
return isStudent ? $currentOrg.customization.course.grading : true;
}
},
{
label: $t('course.navItems.nav_people'),
@@ -14,7 +14,6 @@
import Share from 'carbon-icons-svelte/lib/Share.svelte';
import UserFollow from 'carbon-icons-svelte/lib/UserFollow.svelte';
import { t } from '$lib/utils/functions/translations';
import { onDestroy } from 'svelte';
export let bannerImage: string | undefined;
export let id = '';
@@ -53,6 +52,12 @@
// TODO: Delete course functionality
alert('WIP: Delete course');
}
function getCourseUrl() {
return isOnLandingPage
? `/course/${slug}`
: `/courses/${id}${isLMS ? '/lessons?next=true' : ''}`;
}
</script>
{#if !isLMS && !isOnLandingPage}
@@ -87,7 +92,7 @@
<a
rel="prefetch"
bind:this={target}
href={isOnLandingPage ? `/course/${slug}` : `/courses/${id}${isLMS ? '/lessons?next=true' : ''}`}
href={getCourseUrl()}
class="text-black border border-gray dark:border-neutral-600 rounded w-full max-w-[320px] relative hover:scale-95 transition-all ease-in-out"
>
<div class="p-4">
@@ -31,11 +31,11 @@
<ToolTip
title={toolTipProps.title}
hotkeys={toolTipProps.hotkeys}
hotkeys={disabled ? [] : toolTipProps.hotkeys}
direction={toolTipProps.direction}
>
<button
class="root {color} {selected && 'active'} {size} {contained &&
class="root {color} {selected && 'active'} {disabled && 'disabled'} {size} {contained &&
'contained dark:bg-neutral-700'} {buttonClassName}"
{disabled}
{type}
@@ -65,6 +65,11 @@
-webkit-tap-highlight-color: transparent;
width: fit-content;
&.disabled {
cursor: not-allowed;
color: rgba(0, 0, 0, 0.07);
}
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
@@ -11,11 +11,13 @@
import { NavClasses } from '$lib/utils/constants/reusableClass';
import { sideBar } from '$lib/components/Org/store';
import { t } from '$lib/utils/functions/translations';
import { currentOrg } from '$lib/utils/store/org';
interface sideLinks {
name: string;
icon: any;
link: string;
show?: () => boolean;
}
function isActive(pagePath: string, itemPath: string) {
@@ -45,14 +47,21 @@
{
name: $t('lms_navigation.exercise'),
icon: LicenseDraft,
link: '/lms/exercises'
link: '/lms/exercises',
show() {
return $currentOrg?.customization?.dashboard?.exercise;
}
},
{
name: $t('lms_navigation.community'),
icon: CommunityIcon,
link: '/lms/community'
link: '/lms/community',
show() {
return $currentOrg?.customization?.dashboard?.community;
}
}
];
].filter((link) => (link.show ? link.show() : true));
const toggleSidebar = () => {
$sideBar.hidden = !$sideBar.hidden;
};
@@ -0,0 +1,184 @@
<script lang="ts">
import { Grid, Row, Column } from 'carbon-components-svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import Save from 'carbon-icons-svelte/lib/Save.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import SectionTitle from '../SectionTitle.svelte';
import { Toggle } from 'carbon-components-svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import { currentOrg } from '$lib/utils/store/org';
import { getSupabase } from '$lib/utils/functions/supabase';
import { snackbar } from '$lib/components/Snackbar/store';
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
import { t } from '$lib/utils/functions/translations';
const supabase = getSupabase();
let widgetKey = '';
let isSaving = false;
function widgetControl(key: string) {
widgetKey = key;
$handleOpenWidget.open = true;
}
async function handleSave() {
isSaving = true;
const { error } = await supabase
.from('organization')
.update({ customization: $currentOrg.customization })
.match({ id: $currentOrg.id });
if (error) {
const message = error?.message || 'snackbar.lms.error.try_again';
console.error('Error updating customizations', message);
snackbar.error('snackbar.lms.error.update');
} else {
snackbar.success('snackbar.success_update');
}
isSaving = false;
}
</script>
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}>
<SectionTitle>{$t('components.settings.customize_lms.dashboard.title')}</SectionTitle>
</Column>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<div>
<p>{$t('components.settings.customize_lms.dashboard.community')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.dashboard.community}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div>
<p>{$t('components.settings.customize_lms.dashboard.exercises')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.dashboard.exercise}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div class="mb-3">
<p>{$t('components.settings.customize_lms.dashboard.banner_image')}</p>
<PrimaryButton
variant={VARIANTS.OUTLINED}
label={$t('components.settings.customize_lms.dashboard.banner_image_btn')}
className="mt-3"
onClick={() => widgetControl('banner-image')}
/>
{#if $currentOrg.customization.dashboard.bannerImage}
<img
alt="Banner"
src={$currentOrg.customization.dashboard.bannerImage}
class="mt-2 rounded-md w-full"
/>
{/if}
{#if $handleOpenWidget.open && widgetKey === 'banner-image'}
<UploadWidget bind:imageURL={$currentOrg.customization.dashboard.bannerImage} />
{/if}
</div>
<div>
<p>{$t('components.settings.customize_lms.dashboard.banner_text')}</p>
<TextField
label={$t('components.settings.customize_lms.dashboard.banner_text_label')}
placeholder={$t('components.settings.customize_lms.dashboard.banner_text_placeholder')}
className="mb-5"
bind:value={$currentOrg.customization.dashboard.bannerText}
/>
</div>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('components.settings.customize_lms.course.title')}</SectionTitle></Column
>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<div>
<p>{$t('components.settings.customize_lms.course.newsfeed')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.course.newsfeed}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div>
<p>{$t('components.settings.customize_lms.course.grading')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.course.grading}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('components.settings.customize_lms.apps.title')}</SectionTitle></Column
>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<div>
<p>{$t('components.settings.customize_lms.apps.poll')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.apps.poll}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
<div>
<p>{$t('components.settings.customize_lms.apps.live_comment')}</p>
<Toggle size="sm" class="mb-3" bind:toggled={$currentOrg.customization.apps.comments}>
<span slot="labelA" style="color: gray"
>{$t('components.settings.customize_lms.disabled')}</span
>
<span slot="labelB" style="color: gray"
>{$t('components.settings.customize_lms.enabled')}</span
>
</Toggle>
</div>
</Column>
</Row>
<div class="hidden md:block sticky float-right bottom-12 mr-2 z-[120]">
<PrimaryButton
label={$t('components.settings.customize_lms.save')}
onClick={handleSave}
isLoading={isSaving}
isDisabled={isSaving}
/>
</div>
</Grid>
<div class="absolute block md:hidden right-6 bottom-8 z-[120]">
<PrimaryButton onClick={handleSave} isLoading={isSaving} isDisabled={isSaving}>
<Save size={24} class="" />
</PrimaryButton>
</div>
@@ -96,7 +96,7 @@
snackbar.error(`snackbar.lms.error.update ${message}`);
} else {
$currentOrg.landingpage = $landingPageSettings;
snackbar.success();
snackbar.success('snackbar.success_update');
}
isSaving = false;
@@ -115,7 +115,7 @@
}
}
$: setDefault($currentOrg?.landingpage as OrgLandingPageJson);
$: setDefault($currentOrg?.landingpage as unknown as OrgLandingPageJson);
</script>
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
@@ -177,6 +177,28 @@
</div>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle
>{$t('settings.organization.organization_profile.customize_lms.heading')}</SectionTitle
></Column
>
<Column sm={8} md={8} lg={8}>
<h4 class="dark:text-white lg:mt-0">
{$t('settings.organization.organization_profile.customize_lms.sub_heading')}
</h4>
<p class="text-sm text-gray-500 dark:text-white">
{$t('settings.organization.organization_profile.customize_lms.body')}
</p>
<PrimaryButton
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
variant={VARIANTS.OUTLINED}
onClick={() => gotoSetting('/customize-lms')}
>
{$t('settings.organization.organization_profile.customize_lms.button')}
</PrimaryButton>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle
@@ -63,6 +63,7 @@ export interface OrgLandingPageJson {
show: boolean;
};
}
export let landingPageSettings: Writable<OrgLandingPageJson> = writable({
header: {
title: 'Web Development Courses for',