CLASSROOMIO-T-211: Add a content-switcher in the landing page settings for banner

CLASSROOMIO-T-211: Remove business logic of uploading image

CLASSROOMIO-T-211:Updated the UI of the landingpage settings

Merge branch 'main' into CLASSROOMIO-T-211

CLASSROOMIO-T-211:Added a Content-switch in the landingpage settings


Co-authored-by: Ifeanyi Nwosu <ifeanyi@digdippa.com>
Co-authored-by: Rotimi Best <rotimiibitoyeemma@gmail.com>


Merge-request: CLASSROOMIO-MR-97
Merged-by: Best Admin <best@digdippa.com>
This commit is contained in:
emmanuel
2023-10-11 11:40:08 +00:00
committed by Space Cloud
co-authored by Rotimi Best
parent 27b0284538
commit b676993c2d
11 changed files with 177 additions and 173 deletions
@@ -13,8 +13,6 @@
import { course } from '$lib/components/Course/store';
import { updateCourse, deleteCourse } from '$lib/utils/services/courses';
import { currentOrgPath } from '$lib/utils/store/org';
import { supabase } from '$lib/utils/functions/supabase';
import { Loading } from 'carbon-components-svelte';
import { isObject } from '$lib/utils/functions/isObject';
import { lessons } from '../Lesson/store/lessons';
import { currentOrg } from '$lib/utils/store/org';
@@ -22,14 +20,11 @@
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
let uploadingImage = false;
let isSaving = false;
let fileInput;
let isLoading = false;
let isDeleting = false;
let errors = {};
let avatar;
let imagebuffer;
// controls widget open and close
function widgetControl() {
@@ -120,7 +115,7 @@
const {
course_title,
course_description,
image,
logo,
tabs,
grading,
lesson_download,
@@ -129,7 +124,7 @@
await updateCourse($course.id, avatar, {
title: course_title,
description: course_description,
logo: image,
logo: logo,
is_published,
metadata: {
...(isObject($course.metadata) ? $course.metadata : {}),
@@ -141,7 +136,7 @@
$course.title = course_title;
$course.description = course_description;
$course.logo = image;
$course.logo = logo;
$course.is_published = is_published;
$course.metadata = {
...(isObject($course.metadata) ? $course.metadata : {}),
@@ -163,7 +158,7 @@
$settings = {
course_title: course.title,
course_description: course.description,
image: course.logo,
logo: course.logo,
tabs: course.metadata.lessonTabsOrder || $settings.tabs,
grading: course.metadata.grading,
lesson_download: course.metadata.lessonDownload,
@@ -199,7 +194,7 @@
/>
</span>
{#if $handleOpenWidget.open}
<UploadWidget />
<UploadWidget bind:imageURL={$settings.logo} />
{/if}
</Column>
@@ -208,12 +203,9 @@
<img
style="min-width:280px; min-height:200px"
alt="About us"
src={$settings.image ? $settings.image : '/images/classroomio-course-img-template.jpg'}
src={$settings.logo ? $settings.logo : '/images/classroomio-course-img-template.jpg'}
class="mt-2 md:mt-0 w-[280px] h-[200px] rounded-md relative"
/>
{#if uploadingImage}
<Loading withOverlay={true} small class="absolute top-0 w-[280px]" />
{/if}
</div>
</Column>
</Row>
@@ -1,7 +1,7 @@
import { writable } from 'svelte/store';
export const settings = writable({
image: '',
logo: '',
course_title: '',
course_description: '',
grading: false,
@@ -1,12 +1,8 @@
<script lang="ts">
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import get from 'lodash/get';
import { onMount, onDestroy } from 'svelte';
import pluralize from 'pluralize';
import { page } from '$app/stores';
import { landingPage } from './store';
import { Loading } from 'carbon-components-svelte';
import Notebook from 'carbon-icons-svelte/lib/Notebook.svelte'; //note
import PresentationFile from 'carbon-icons-svelte/lib/PresentationFile.svelte'; // exercise
import Video from 'carbon-icons-svelte/lib/Video.svelte'; //video
@@ -26,7 +22,6 @@
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
import { calDateDiff } from '$lib/utils/functions/date';
import { handleOpenWidget } from './store';
export let editMode = false;
export let courseData: Course;
@@ -128,10 +123,6 @@
startCoursePayment = true;
}}
/>
{#if $handleOpenWidget.open}
<UploadWidget />
{/if}
</div>
<!-- Banner Image getEmbedId(videoUrl) -->
@@ -161,9 +152,6 @@
src={bannerImage ? bannerImage : '/images/classroomio-course-img-template.jpg'}
class="mt-2 md:mt-0 rounded-md w-full h-full max-w-[500px] max-h-[400px] relative"
/>
{#if $landingPage.uploadingImage}
<Loading withOverlay={true} small class="absolute top-0 w-full h-full" />
{/if}
</div>
<!-- <div
@@ -60,11 +60,6 @@ export const reviewsModalStore = writable({
open: false
});
export const landingPage = writable({
uploadingImage: false,
imageUrl: ''
});
export let handleOpenWidget = writable({
open: false
});
+23 -11
View File
@@ -132,11 +132,17 @@
// Use default data in store if user hasn't added their own content to landing page
function setDefault(landingpage) {
if (landingpage && Object.keys(landingpage).length) {
$landingPageSettings = landingpage;
if (!landingpage?.header?.banner) {
landingpage.header.banner = $landingPageSettings.header.banner;
}
$landingPageSettings = {
...landingpage
};
}
}
$: initPlyr(player, $landingPageSettings.header.video.link);
$: initPlyr(player, $landingPageSettings.header?.banner?.video);
$: setDefault(org.landingpage);
</script>
@@ -155,7 +161,7 @@
<header id="header" class="banner w-full h-[100vh] md:h-[90vh] mb-10 relative">
<Navigation logo={org.avatar_url} orgName={org.name} disableSignup={true} />
<div class="absolute h-[100vh] md:h-[90vh] top-0 w-full opacity-80 z-10 bg-white" />
{#if $landingPageSettings.header.video.show}
{#if $landingPageSettings.header.banner.show}
<div class="flex items-center justify-center md:h-full py-2">
<div
class="md:w-11/12 lg:w-5/6 w-full flex items-center justify-center md:justify-between flex-col-reverse md:flex-row z-20 relative"
@@ -185,24 +191,30 @@
}}
/>
</div>
<div class="w-5/6 md:w-1/2 md:max-w-[650px] flex">
{#if isYouTubeLink($landingPageSettings.header.video.link)}
{#if isYouTubeLink($landingPageSettings.header?.banner?.video) && $landingPageSettings.header.banner.type === 'video'}
<div bind:this={player} id="player" style="width:100%; border-radius:12px">
<iframe
title="Header video"
src={$landingPageSettings.header.video.link}
src={$landingPageSettings.header?.banner?.video}
allowfullscreen
allowtransparency
allow="autoplay"
/>
</div>
{:else}
<video class="w-full rounded-xl" controls loop autoplay>
<source src={$landingPageSettings.header.video.link} type="video/mp4" />
<!-- <source src="/path/to/video.webm" type="video/webm" /> -->
<!-- Captions are optional -->
<track kind="captions" />
</video>
<!-- <video class="w-full rounded-xl" controls loop autoplay>
<source src={$landingPageSettings.header?.banner?.video} type="video/mp4" />
<source src="/path/to/video.webm" type="video/webm" />
Captions are optional
<track kind="captions" />
</video> -->
<img
class="rounded-md"
src={$landingPageSettings.header?.banner?.image}
alt="landing page banner"
/>
{/if}
</div>
</div>
@@ -1,5 +1,5 @@
<script lang="ts">
import { Grid, Row, Column } from 'carbon-components-svelte';
import { Grid, Row, Column, RadioButtonGroup, RadioButton } from 'carbon-components-svelte';
import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
@@ -14,13 +14,21 @@
import { FileUploader } from 'carbon-components-svelte';
import { landingPageSettings } from './store';
import type { OrgLandingPageJson } from './store';
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
import merge from 'lodash/merge';
let files: File[] = [];
let imageBuffer: File;
let uploadingImage = false;
let isSaving = false;
let creatingNewQuestion = false;
let widgetKey = '';
const banner = [
{ value: 'video', label: 'Video' },
{ value: 'image', label: 'Image' }
];
const supabase = getSupabase();
let newQuestion = {
@@ -28,6 +36,11 @@
content: ''
};
function widgetControl(key: string) {
widgetKey = key;
$handleOpenWidget.open = true;
}
function createNewFaq() {
newQuestion = {
title: '',
@@ -60,34 +73,6 @@
$landingPageSettings.faq.questions = filteredFaq;
}
const uploadImage = async (image: File) => {
if (image) {
const filename = `landingpage/${Date.now()}` + image.name;
const { data } = await supabase.storage.from('avatars').upload(filename, image, {
cacheControl: '3600',
upsert: false
});
if (data) {
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
$landingPageSettings.aboutUs.imageUrl = response.publicUrl;
uploadingImage = false;
}
}
};
const onFileSelected = (image: File) => {
uploadingImage = true;
let reader = new FileReader();
reader.readAsDataURL(image);
reader.onload = (e) => {
imageBuffer = image;
uploadImage(image);
};
};
const checkPrefix = (inputValue: string) => {
if (!inputValue) return;
@@ -98,6 +83,7 @@
}
return inputValue;
};
async function handleSave() {
isSaving = true;
$landingPageSettings.footer.twitter = checkPrefix($landingPageSettings.footer.twitter) || '';
@@ -122,11 +108,18 @@
function setDefault(landingpage: OrgLandingPageJson) {
if (landingpage && Object.keys(landingpage).length) {
$landingPageSettings = landingpage;
// Added new key, support backward compatibility
if (!landingpage?.header?.banner) {
landingpage.header.banner = $landingPageSettings.header.banner;
}
$landingPageSettings = {
...landingpage
};
}
}
$: setDefault($currentOrg?.landingpage);
$: setDefault($currentOrg?.landingpage as OrgLandingPageJson);
</script>
<Grid class="border-c rounded border-gray-200 w-full mt-5 relative">
@@ -179,18 +172,48 @@
<span slot="labelB" style="color: gray">Redirect</span>
</Toggle>
</div>
<SectionTitle>Video</SectionTitle>
<div class="gap-2 mt-3 mb-5">
<TextField
label="Link"
placeholder="Write your video link here"
bind:value={$landingPageSettings.header.video.link}
<div />
<RadioButtonGroup
legendText="Banner Type"
bind:selected={$landingPageSettings.header.banner.type}
class="mt-10 mb-5"
>
{#each banner as item}
<RadioButton value={item.value} labelText={item.label} />
{/each}
</RadioButtonGroup>
{#if $landingPageSettings.header.banner.type === 'video'}
<div class="gap-2 mt-3 mb-5">
<TextField
label="Link"
placeholder="Write your video link here"
bind:value={$landingPageSettings.header.banner.video}
/>
</div>
{:else}
<PrimaryButton
variant={VARIANTS.OUTLINED}
label="Select Image"
className="mt-3"
onClick={() => widgetControl('banner')}
/>
<Toggle bind:toggled={$landingPageSettings.header.video.show} size="sm">
<span slot="labelA" style="color: gray">Hide Video</span>
<span slot="labelB" style="color: gray">Show Video</span>
</Toggle>
</div>
{/if}
{#if $landingPageSettings.header.banner.image && $landingPageSettings.header.banner.type === 'image'}
<img
alt="bannerImage"
src={$landingPageSettings.header.banner.image}
class="mt-2 rounded-md w-full"
/>
{/if}
<Toggle bind:toggled={$landingPageSettings.header.banner.show} size="sm">
<span slot="labelA" style="color: gray">Hide banner</span>
<span slot="labelB" style="color: gray">Show banner</span>
</Toggle>
{#if $handleOpenWidget.open && widgetKey === 'banner'}
<UploadWidget bind:imageURL={$landingPageSettings.header.banner.image} />
{/if}
</Column>
</Row>
@@ -220,14 +243,12 @@
/>
<div>
<FileUploader
status={uploadingImage ? 'uploading' : 'complete'}
buttonLabel="Select file"
labelTitle="Upload an image"
class="flex-col items-start gap-2 w-full"
accept={['.jpg', '.jpeg', '.png']}
bind:files
on:change={(file) => onFileSelected(file?.detail?.[0])}
<p class="font-bold">Upload an image</p>
<PrimaryButton
variant={VARIANTS.OUTLINED}
label="Select Image"
className="mt-3"
onClick={() => widgetControl('about-us')}
/>
{#if $landingPageSettings.aboutUs.imageUrl}
<img
@@ -236,6 +257,9 @@
class="mt-2 rounded-md w-full"
/>
{/if}
{#if $handleOpenWidget.open && widgetKey === 'about-us'}
<UploadWidget bind:imageURL={$landingPageSettings.aboutUs.imageUrl} />
{/if}
</div>
</Column>
</Row>
+9 -5
View File
@@ -11,8 +11,10 @@ export interface OrgLandingPageJson {
link: string;
redirect: boolean;
};
video: {
link: string;
banner: {
video: string;
image: string;
type: string;
show: boolean;
};
show: boolean;
@@ -72,9 +74,11 @@ export let landingPageSettings: Writable<OrgLandingPageJson> = writable({
link: '#contact',
redirect: false
},
video: {
show: true,
link: 'https://www.youtube.com/watch?v=qqAYW7uxErI'
banner: {
video: 'https://www.youtube.com/watch?v=qqAYW7uxErI',
image: '',
type: 'video',
show: true
},
show: true
},
+11 -5
View File
@@ -1,9 +1,15 @@
<script>
<script lang="ts">
import TextChip from '$lib/components/Chip/Text.svelte';
export let tabs = [];
export let currentTab;
export let onChange;
interface Tab {
icon?: any;
label: string;
value: string;
badgeValue?: number;
}
export let tabs: Tab[] = [];
export let currentTab: string;
export let onChange = (v: string) => () => {};
</script>
<div class="w-full flex flex-col">
@@ -40,7 +46,7 @@
{tab.label}
{#if typeof tab.badgeValue === 'number'}
<TextChip
value={tab.badgeValue}
value={`${tab.badgeValue}`}
size="sm"
shape="rounded-full"
className="bg-gray-300 dark:text-black text-xs absolute right-0 px-2"
+45 -65
View File
@@ -1,91 +1,69 @@
<script lang="ts">
import { onMount } from 'svelte';
import Tabs from '$lib/components/Tabs/index.svelte';
import { course } from '$lib/components/Course/store';
import { getSupabase } from '$lib/utils/functions/supabase';
import TabContent from '$lib/components/TabContent/index.svelte';
import { updateCourse } from '$lib/utils/services/courses';
import { snackbar } from '../Snackbar/store';
import { isObject } from '$lib/utils/functions/isObject';
import Modal from '$lib/components/Modal/index.svelte';
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
import { settings } from '../Course/components/Settings/store';
import { queryUnsplash } from './utils';
export let selectedImgUrl = '';
export let imageURL = '';
let tabs = [
const supabase = getSupabase();
const tabs = [
{ label: 'Unsplash', value: 'unsplash' },
{ label: 'Upload', value: 'upload' }
];
let currentTab = tabs[0].value;
let searchQuery = '';
let unsplashImages = [];
let avatar;
let fileInput;
let unsplashImages: {
id: string | number;
urls: {
regular: string;
};
alt_description: string;
}[] = [];
let fileInput: HTMLInputElement;
let imagebuffer;
const supabase = getSupabase();
const onChange = (tabValue) => {
currentTab = tabValue;
};
const onChange = (tabValue: string) => () => (currentTab = tabValue);
async function handleImageClick(imageUrl) {
selectedImgUrl = imageUrl;
uploadImage(selectedImgUrl);
async function handleImageClick(img: string) {
imageURL = img;
$handleOpenWidget.open = false;
}
const onFileSelected = () => {
const image = fileInput.files[0];
if (image) {
const file = fileInput?.files?.[0];
if (file) {
let reader = new FileReader();
reader.readAsDataURL(image);
reader.readAsDataURL(file);
reader.onload = (e) => {
imagebuffer = image;
uploadImage(image);
imagebuffer = file;
uploadImage(file);
};
}
};
const uploadImage = async (image) => {
if (image) {
if (typeof image === 'string') {
$settings.image = image;
} else if (image instanceof File) {
const filename = `bannerimage/${Date.now()}` + image.name;
const { data } = await supabase.storage.from('avatars').upload(filename, image, {
cacheControl: '3600',
upsert: false
});
if (data) {
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
$settings.image = response.publicUrl;
}
}
try {
const { image } = $settings;
await updateCourse($course.id, avatar, {
logo: image,
metadata: {
...(isObject($course.metadata) ? $course.metadata : {})
}
});
$course.logo = image;
$course.metadata = {
...(isObject($course.metadata) ? $course.metadata : {})
};
snackbar.success('Saved successfully');
$handleOpenWidget.open = false;
} catch (error) {
snackbar.error();
console.log(error);
}
const uploadImage = async (image: File) => {
if (!image) {
return;
}
const filename = `uploadwidget/${Date.now()}` + image.name;
const { data } = await supabase.storage.from('avatars').upload(filename, image, {
cacheControl: '3600',
upsert: false
});
if (data) {
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
imageURL = response.publicUrl;
}
snackbar.success(`Complete :)`);
$handleOpenWidget.open = false;
};
function handleUpload() {
@@ -96,6 +74,7 @@
try {
unsplashImages = await queryUnsplash(searchQuery || 'rocks');
} catch (error) {
snackbar.error('Error fetching images from Unsplash');
console.error('Error fetching images from Unsplash:', error);
}
}
@@ -153,12 +132,13 @@
<div class="flex flex-row items-center flex-wrap gap-2 py-4 px-[10px] max-h-[300px]">
{#each unsplashImages as unsplashImages (unsplashImages.id)}
<div class="w-[190px] h-[130px] overflow-hidden relative">
<img
src={unsplashImages.urls.regular}
alt={unsplashImages.alt_description}
class="w-full h-full object-cover rounded-md hover:opacity-80 cursor-pointer"
on:click={() => handleImageClick(unsplashImages.urls.regular)}
/>
<button on:click={() => handleImageClick(unsplashImages.urls.regular)}>
<img
src={unsplashImages.urls.regular}
alt={unsplashImages.alt_description}
class="w-full h-full object-cover rounded-md hover:opacity-80 cursor-pointer"
/>
</button>
</div>
{/each}
</div>
@@ -8,9 +8,10 @@
"link": "#contact",
"redirect": false
},
"video": {
"show": false,
"link": "https://youtube.com/sosf24lsfgs"
"banner": {
"video": "https://youtube.com/sosf24lsfgs",
"image": "",
"show": false
},
"show": true
},
@@ -64,4 +65,4 @@
"instagram": "#",
"linkedin": "#"
}
}
}
+3 -1
View File
@@ -6,7 +6,9 @@ export interface CurrentOrg {
avatar_url: string;
memberId: string;
role_id: string;
landingpage: {};
landingpage: {
[key: string]: any;
};
theme: string;
}