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