CLASSROOMIO-T-113: Settings for landing page

Merge-request: CLASSROOMIO-MR-23
Merged-by: Best Admin <[email protected]>

Co-authored-by: Ifeanyi Nwosu <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>


Merge-request: CLASSROOMIO-MR-50
Merged-by: Best Admin <[email protected]>
This commit is contained in:
tunny17
2023-08-21 15:44:15 +00:00
committed by Space Cloud
co-authored by Ifeanyi Nwosu Rotimi Best
parent 2b36d0af67
commit 32d0198d3d
9 changed files with 618 additions and 36 deletions
@@ -0,0 +1,434 @@
<script lang="ts">
import { Grid, Row, Column } 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';
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 TextArea from '$lib/components/Form/TextArea.svelte';
import { currentOrg } from '$lib/utils/store/org';
import { getSupabase } from '$lib/utils/functions/supabase';
import { snackbarStore } from '$lib/components/Snackbar/store';
import { SNACKBAR_SEVERITY } from '$lib/components/Snackbar/constants';
import { FileUploader } from 'carbon-components-svelte';
import { landingPageSettings } from './store';
let files: File[] = [];
let imageBuffer: File;
let uploadingImage = false;
let isSaving = false;
let creatingNewQuestion = false;
const supabase = getSupabase();
let newQuestion = {
title: '',
content: ''
};
function createNewFaq() {
newQuestion = {
title: '',
content: ''
};
creatingNewQuestion = true;
}
function saveNewFAQ() {
if (newQuestion.title !== '' && newQuestion.content !== '') {
$landingPageSettings.faq.questions = [
...$landingPageSettings.faq.questions,
{
id: new Date().getTime(),
title: newQuestion.title,
content: newQuestion.content
}
];
creatingNewQuestion = false;
}
}
function cancelNewFAQ() {
creatingNewQuestion = false;
}
function deleteFaq(id: number) {
let faqs = $landingPageSettings.faq.questions;
const filteredFaq = faqs.filter((faq) => faq.id !== id);
$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);
};
};
async function handleSave() {
isSaving = true;
const { data, error } = await supabase
.from('organization')
.update({ landingpage: $landingPageSettings })
.match({ id: $currentOrg.id });
if (error) {
let message = error?.message || 'Please try again';
$snackbarStore.open = true;
$snackbarStore.message = `Update failed: ${message}`;
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
} else {
$currentOrg.landingpage = $landingPageSettings;
$snackbarStore.open = true;
$snackbarStore.message = 'Saved successful';
$snackbarStore.severity = SNACKBAR_SEVERITY.SUCCESS;
}
isSaving = false;
}
function setDefault(landingpage) {
if (landingpage && Object.keys(landingpage).length) {
$landingPageSettings = landingpage;
}
}
$: setDefault($currentOrg.landingpage);
</script>
<Grid class="border-c rounded border-gray-200 w-full mt-5 relative">
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}>
<SectionTitle>Landing Page</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.header.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
</Toggle>
</Column>
<Column sm={2} md={6} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
bind:value={$landingPageSettings.header.title}
className="mb-5"
/>
<TextField
label="Title-Highlight"
placeholder="Write your titlehighlight here"
className="mb-5"
bind:value={$landingPageSettings.header.titleHighlight}
/>
<TextArea
label="Subtitle"
labelClassName="font-light"
placeholder="Write your subtitle here"
className="mb-5"
bind:value={$landingPageSettings.header.subtitle}
/>
<SectionTitle>Actions</SectionTitle>
<TextField
label="Label"
placeholder="Write your label here"
className="mt-3 mb-5"
bind:value={$landingPageSettings.header.action.label}
/>
<div class="gap-2 mb-5">
<TextField
label="Link"
placeholder="Write your link here"
bind:value={$landingPageSettings.header.action.link}
/>
<Toggle bind:toggled={$landingPageSettings.header.action.redirect} size="sm">
<span slot="labelA" style="color: red">No redirect</span>
<span slot="labelB" style="color: green">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}
/>
<Toggle bind:toggled={$landingPageSettings.header.video.show} size="sm">
<span slot="labelA" style="color: red">Hide Video</span>
<span slot="labelB" style="color: green">Show Video</span>
</Toggle>
</div>
</Column>
</Row>
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}
><SectionTitle>About</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.aboutUs.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
</Toggle></Column
>
<Column sm={2} md={6} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
bind:value={$landingPageSettings.aboutUs.title}
className="mb-5"
/>
<TextArea
label="Content"
labelClassName="font-light"
placeholder="Write your Content here"
className="mb-5"
bind:value={$landingPageSettings.aboutUs.content}
/>
<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])}
/>
{#if $landingPageSettings.aboutUs.imageUrl}
<img
alt="About us"
src={$landingPageSettings.aboutUs.imageUrl}
class="mt-2 rounded-md w-full"
/>
{/if}
</div>
</Column>
</Row>
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}
><SectionTitle>Courses</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.courses.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
</Toggle>
</Column>
<Column sm={2} md={6} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
bind:value={$landingPageSettings.courses.title}
/>
<TextField
label="Title-Highlight"
placeholder="Write your title highlight here"
bind:value={$landingPageSettings.courses.titleHighlight}
/>
<TextArea
label="Subtitle"
labelClassName="font-light"
placeholder="Write your subtitle here"
bind:value={$landingPageSettings.courses.subtitle}
/>
</Column>
</Row>
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}
><SectionTitle>Frequently Asked Questions</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.faq.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
</Toggle>
</Column>
<Column sm={2} md={6} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
bind:value={$landingPageSettings.faq.title}
className="mb-5"
/>
{#each $landingPageSettings.faq.questions as item (item.id)}
<div class="mb-3">
<TextField
label="Question"
placeholder="Write your question here"
bind:value={item.title}
className="mb-5"
/>
<TextArea
label="Answer"
labelClassName="font-light"
placeholder="Write your answer here"
bind:value={item.content}
className="mb-5"
/>
<IconButton onClick={() => deleteFaq(item.id)}>
<TrashCan size={24} class="fill-red-700" />
</IconButton>
</div>
{/each}
{#if creatingNewQuestion}
<TextField
label="Question"
placeholder="Write your question here"
bind:value={newQuestion.title}
className="mb-5"
/>
<TextArea
label="Answer"
labelClassName="font-light"
placeholder="Write your answer here"
bind:value={newQuestion.content}
className="mb-5"
/>
<div class="flex items-center gap-2">
<PrimaryButton variant={VARIANTS.OUTLINED} label="Save" onClick={saveNewFAQ} />
<PrimaryButton variant={VARIANTS.OUTLINED} label="Cancel" onClick={cancelNewFAQ} />
</div>
{:else}
<PrimaryButton variant={VARIANTS.OUTLINED} label="Add New Faq" onClick={createNewFaq} />
{/if}
</Column>
</Row>
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}
><SectionTitle>Contact us</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.contact.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
</Toggle>
</Column>
<Column sm={2} md={6} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
bind:value={$landingPageSettings.contact.title}
className="mb-5"
/>
<TextField
label="Title-highlight"
labelClassName="font-light"
placeholder="Write your title highlight here"
className="mb-5"
bind:value={$landingPageSettings.contact.titleHighlight}
/>
<TextArea
label="Subtitle"
labelClassName="font-light"
placeholder="Write your subtitle here"
className="mt-3 mb-5"
bind:value={$landingPageSettings.contact.subtitle}
/>
<TextField
label="Phone number"
placeholder="Write your phone number here"
className="mt-3 mb-5"
bind:value={$landingPageSettings.contact.phone}
/>
<TextField
label="Email"
placeholder="Write your email here"
className="mt-3 mb-5"
bind:value={$landingPageSettings.contact.email}
/>
</Column>
</Row>
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}
><SectionTitle>Mailing List</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.mailinglist.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
</Toggle>
</Column>
<Column sm={2} md={6} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
bind:value={$landingPageSettings.mailinglist.title}
className="mb-5"
/>
<TextArea
label="Subtitle"
labelClassName="font-light"
placeholder="Write your subtitle here"
className="mb-5"
bind:value={$landingPageSettings.mailinglist.subtitle}
/>
<TextField
label="Button-label"
placeholder="Write your label here"
className="mb-5"
bind:value={$landingPageSettings.mailinglist.buttonLabel}
/>
</Column>
</Row>
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}
><SectionTitle>Footer</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.footer.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
</Toggle>
</Column>
<Column sm={2} md={6} lg={8}>
<TextField
label="Facebook"
placeholder="Write your Facebook link here"
bind:value={$landingPageSettings.footer.facebook}
className="mb-5"
/>
<TextField
label="Instagram"
placeholder="Write your Instagram link here"
className="mb-5"
bind:value={$landingPageSettings.footer.instagram}
/>
<TextField
label="Linkedin"
placeholder="Write your Linkedin link here"
className="mb-5"
bind:value={$landingPageSettings.footer.linkedin}
/>
</Column>
</Row>
<Row class="p-5 w-full flex items-center justify-center">
<PrimaryButton
label="Save Changes"
isLoading={isSaving}
isDisabled={isSaving}
onClick={handleSave}
/>
</Row>
</Grid>
@@ -58,8 +58,8 @@
if (data && data.Key) {
const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
updates.avatar_url = publicURL;
$currentOrg.avatar_url = publicURL;
updates.avatar_url = data.publicUrl;
$currentOrg.avatar_url = data.publicUrl;
}
avatar = undefined;
}
@@ -36,12 +36,11 @@
cacheControl: '3600',
upsert: false
});
if (data) {
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
if (data && data.Key) {
const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
updates.avatar_url = publicURL;
$profile.avatar_url = publicURL;
updates.avatar_url = response.publicUrl;
$profile.avatar_url = response.publicUrl;
}
avatar = undefined;
}
+12 -6
View File
@@ -2,6 +2,7 @@
import { page } from '$app/stores';
import { Tabs, Tab, TabContent } from 'carbon-components-svelte';
import Profile from './Profile.svelte';
import LandingpageSettings from './LandingpageSettings.svelte';
// import Account from './Account.svelte';
import OrgSettings from './OrgSettings.svelte';
import { onMount } from 'svelte';
@@ -21,20 +22,22 @@
},
{
key: 1,
label: 'Organization Settings',
label: 'Organization',
tabKey: 'org',
href: `${$page.url.pathname}?tab=org`
},
{
key: 2,
label: 'LandingPage',
tabKey: 'landingpage',
href: `${$page.url.pathname}?tab=landingpage`
}
];
function getSelectedByTab(tabKey = '') {
const tab = tabs.find((t) => t.tabKey === tabKey);
console.log('tab', tab);
if (tab) {
return tab.key;
}
return 0;
return tab ? tab.key : 0;
}
function changeRouteOnTabChange(key = 0) {
const tab = tabs.find((t) => t.key === key);
@@ -69,5 +72,8 @@
<TabContent>
<OrgSettings />
</TabContent>
<TabContent>
<LandingpageSettings />
</TabContent>
</svelte:fragment>
</Tabs>
+139
View File
@@ -0,0 +1,139 @@
import { writable } from 'svelte/store';
import type { Writable } from 'svelte/store';
export interface OrgLandingPageJson {
header: {
title: string;
titleHighlight: string;
subtitle: string;
action: {
label: string;
link: string;
redirect: boolean;
};
video: {
link: string;
show: boolean;
};
show: boolean;
};
aboutUs: {
title: string;
content: string;
imageUrl: string;
show: boolean;
};
courses: {
title: string;
titleHighlight: string;
subtitle: string;
show: boolean;
};
faq: {
title: string;
questions: Array<{
id: number;
title: string;
content: string;
}>;
show: boolean;
};
contact: {
title: string;
titleHighlight: string;
subtitle: string;
address: string;
phone: string;
email: string;
show: boolean;
};
mailinglist: {
title: string;
subtitle: string;
buttonLabel: string;
show: boolean;
};
footer: {
facebook: string;
instagram: string;
linkedin: string;
show: boolean;
};
}
export let landingPageSettings: Writable<OrgLandingPageJson> = writable({
header: {
title: 'Web Development Courses for',
titleHighlight: 'Everyone',
subtitle:
'Practical project-based courses that are easy to understand and straight to the point',
action: {
label: 'Start learning Today',
link: '#contact',
redirect: false
},
video: {
show: false,
link: 'https://youtube.com/sosf24lsfgs'
},
show: false
},
aboutUs: {
title: 'Our Story',
content:
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Repudiandae, quaerat cum. Ullam similique quae dicta ipsum atque quam fugit iusto eligendi magni voluptatum aut, exercitationem deserunt vitae iste rem sunt!\nLorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores obcaecati veritatis tenetur in, nihil repellat quasi cumque sed molestias id reiciendis harum doloremque minus fugit quia cum dolores quas pariatur.',
imageUrl: '',
show: true
},
courses: {
title: 'Explore our',
titleHighlight: 'Courses',
subtitle: 'Find courses you will love from best teachers all over the world🌎.',
show: true
},
faq: {
title: 'Frequently Asked Questions.',
questions: [
{
id: 1,
title: 'Lorem ipsum dolor sit, amet con',
content:
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Repudiandae, quaerat cum. Ullam similique quae dicta ipsum atque quam fugit iusto eligendi magni voluptatum aut, exercitationem deserunt vitae iste rem sunt!\nLorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores obcaecati veritatis tenetur in, nihil repellat quasi cumque sed molestias id reiciendis harum doloremque minus fugit quia cum dolores quas pariatur.'
},
{
id: 2,
title: 'Lorem ipsum dolor sit, amet con',
content:
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Repudiandae, quaerat cum. Ullam similique quae dicta ipsum atque quam fugit iusto eligendi magni voluptatum aut, exercitationem deserunt vitae iste rem sunt!\nLorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores obcaecati veritatis tenetur in, nihil repellat quasi cumque sed molestias id reiciendis harum doloremque minus fugit quia cum dolores quas pariatur.'
},
{
id: 3,
title: 'Lorem ipsum dolor sit, amet con',
content:
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Repudiandae, quaerat cum. Ullam similique quae dicta ipsum atque quam fugit iusto eligendi magni voluptatum aut, exercitationem deserunt vitae iste rem sunt!\nLorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores obcaecati veritatis tenetur in, nihil repellat quasi cumque sed molestias id reiciendis harum doloremque minus fugit quia cum dolores quas pariatur.'
}
],
show: true
},
contact: {
title: 'Have you got any',
titleHighlight: 'Question?',
subtitle: 'Reach out to us with any question you might have.',
address: 'Ashwoodfield House Farm, Kidderminster Rd, Stourbridge, Kingswinford DY6 0AA',
phone: '+4407339904995',
email: '[email protected]',
show: true
},
mailinglist: {
title: 'Join our mailing list',
subtitle:
'We are constantly releasing new courses and sharing them with our email list. Subscribe to get notified once we release a new course',
buttonLabel: 'Subscribe',
show: true
},
footer: {
facebook: '#',
instagram: '#',
linkedin: '#',
show: true
}
});
+8 -8
View File
@@ -93,12 +93,12 @@ export async function uploadAvatar(courseId: string, avatar: string) {
upsert: false
});
if (data && data.Key) {
const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
if (data) {
const { data } = supabase.storage.from('avatars').getPublicUrl(filename);
if (!publicURL) return;
if (!data.publicUrl) return;
logo = publicURL;
logo = data.publicUrl;
}
return logo;
@@ -117,12 +117,12 @@ export async function updateCourse(
upsert: false
});
if (data && data.Key) {
const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
if (data) {
const { data: response } = supabase.storage.from('avatars').getPublicUrl(filename);
if (!publicURL) return;
if (!response.publicUrl) return;
course.logo = publicURL;
course.logo = response.publicUrl;
}
}
+11 -7
View File
@@ -58,8 +58,9 @@ export async function getOrganizations(userId) {
id,
name,
siteName,
theme,
avatar_url
avatar_url,
landingpage,
theme
)
`
)
@@ -75,7 +76,8 @@ export async function getOrganizations(userId) {
siteName: orgMember?.organization?.siteName,
theme: orgMember?.organization?.theme,
avatar_url: orgMember?.organization?.avatar_url,
memberId: orgMember?.id
memberId: orgMember?.id,
landingpage: orgMember?.organization?.landingpage
});
});
@@ -150,8 +152,9 @@ export async function getCourseBySiteName(siteName) {
name: 'ClassroomIO',
shortName: '',
siteName: '',
theme: '',
avatar_url: '/logo-192.png'
avatar_url: '/logo-192.png',
landingpage: {},
theme: ''
});
currentOrg.set(org);
@@ -166,8 +169,9 @@ export async function getCurrentOrg(siteName, justGet = false) {
id,
name,
siteName,
theme,
avatar_url
avatar_url,
landingpage,
theme
`
)
.eq('siteName', siteName);
+4 -4
View File
@@ -1,6 +1,5 @@
import cloneDeep from 'lodash/cloneDeep';
import { writable, derived } from 'svelte/store';
import { allOptions, STEPS, booleanOptions } from '../constants/quiz';
import { STEPS } from '../constants/quiz';
export const orgs = writable([]);
export const currentOrg = writable({
@@ -9,8 +8,9 @@ export const currentOrg = writable({
shortName: '',
siteName: '',
avatar_url: '',
theme: '',
memberId: ''
memberId: '',
landingpage: {},
theme: ''
});
export const orgAudience = writable([]);
export const orgTeam = writable([]);
+4 -4
View File
@@ -58,11 +58,11 @@
upsert: false
});
if (data && data.Key) {
const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
if (data) {
const { data: response } = supabase.storage.from('avatars').getPublicUrl(filename);
updates.avatar_url = publicURL;
currentProfile.avatar_url = publicURL;
updates.avatar_url = response.publicUrl;
currentProfile.avatar_url = response.publicUrl;
}
avatar = undefined;
}