CLASSROOMIO-T-115:Allow Landing page UI to use Org Landing page settings

CLASSROOMIO-T-115: Fetch data on server

CLASSROOMIO-T-115: Add dynamic debug orgs, handle not found orgs, add 404 to public route

CLASSROOMIO-T-115:Automacally add prefix to the footer input

CLASSROOMIO-T-115:Implemented the video to also play youtube videos

Merge branch 'CLASSROOMIO-T-115' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-115
CLASSROOMIO-T-115:Implemented the video to also play youtube videos

CLASSROOMIO-T-115:Still woking on the landing page UI

CLASSROOMIO-T-115: Some improvements

CLASSROOMIO-T-115:Working on the landing page UI

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio

CLASSROOMIO-T-27: Review Section for Instructors on landing page

CLASSROOMIO-T-27: Remove unused variable

Merge branch 'CLASSROOMIO-T-27' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-27

CLASSROOMIO-T-27: Complete reviews functionality

CLASSROOMIO-T-27: Review Render Update

CLASSROOMIO-T-27:
- Resolved Feedbacks

CLASSROOMIO-T-27: Fixed Ratings Data

CLASSROOMIO-T-27:
- Added Ratings Svg
- Fixed avatar_url feedback

CLASSROOMIO-T-27: Import Adjustment

CLASSROOMIO-T-27: Code Review

CLASSROOM-T-27: Added Add Review Functionality

CLASSROOMIO-T-27:
- Unresolved Review
- Enhance Code Redability

CLASSROOMIO-T-27: Completed the delete functionality

CLASSROOMIO-T-27: Hide/Show functionality

CLASSROOMIO-T-27: Double Quotation Update

CLASSROOMIO-T-27: Testing toggle functinality

CLASSROOMIO-T-27: Added the icons

CLASSROOMIO-T-27: Completion of the hide/show functionality

CLASSROOMIO-T-27:
Display the store data on the review page for styling

CLASSROOMIO-T-27:
Styling of the review component & connecting it's data

CLASSROOMIO-T-27:
- Add ReviewsForm
- Completion of the Reviews Form Structure/Styling

CLASSROOMIO-T-27: Added a reviews modal

CLASSROOMIO-T-27: Logic update for review section

CLASSROOMIO-T-50: Logic for overlapping reviews

CLASSROOMIO-T-27:
- Completion of the structure & styling with mock data.
- Addition of custom ratings assets

CLASSROOMIO-T-27:
- Review Section Code Structure
- Import Profile Picture Assets


Co-authored-by: Rotimi Best <[email protected]>
Co-authored-by: Tunji Adeyemi <[email protected]>
Co-authored-by: tunny17 <[email protected]>


Merge-request: CLASSROOMIO-MR-22
Merged-by: Best Admin <[email protected]>
CLASSROOMIO-T-62: Dark Mode Bug Fix

CLASSROOMIO-T-62: Delete Unused class

CLASSROOMIO-T-62: Dark Mode Bug Fix


Co-authored-by: Tunji Adeyemi <[email protected]>


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]>
Co-authored-by: Tunji Adeyemi <[email protected]>
Co-authored-by: tunny17 <[email protected]>


Merge-request: CLASSROOMIO-MR-53
Merged-by: Best Admin <[email protected]>
This commit is contained in:
tunny17
2023-08-24 07:32:19 +00:00
committed by Space Cloud
co-authored by Rotimi Best tunny17 Ifeanyi Nwosu
parent a422f2f290
commit a716c07cf5
11 changed files with 535 additions and 399 deletions
+2 -5
View File
@@ -9,9 +9,7 @@
<CopyCourseModal />
<div
class="flex items-center justify-center lg:justify-start flex-wrap my-4 m-auto"
>
<div class="w-full cards-container my-4 m-auto">
{#if $courseMetaDeta.isLoading}
<CardLoader />
<CardLoader />
@@ -35,8 +33,7 @@
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Courses Created</h3>
<p class="dark:text-white w-1/3 text-center">
Share your knowledge with the world by creating engaging courses for
your students.
Share your knowledge with the world by creating engaging courses for your students.
</p>
</Box>
{/each}
+3 -3
View File
@@ -2,7 +2,7 @@ import { writable } from 'svelte/store';
export const courses = writable([]);
export const courseMetaDeta = writable({
isLoading: false,
isLoading: true
});
export const createCourseModal = writable({
@@ -11,7 +11,7 @@ export const createCourseModal = writable({
description: '',
emails: '',
tutors: '',
students: '',
students: ''
});
export const copyCourseModalInitialState = {
@@ -20,6 +20,6 @@ export const copyCourseModalInitialState = {
id: '',
title: '',
isSaving: false,
error: null,
error: null
};
export const copyCourseModal = writable({ ...copyCourseModalInitialState });
@@ -0,0 +1,28 @@
<script>
import { SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
import CardLoader from '$lib/components/Courses/components/Card/Loader.svelte';
</script>
<section class="w-screen h-screen p-5">
<!-- navbar -->
<div class="flex items-center justify-between w-full rounded-md overflow-hidden">
<SkeletonText style="width:80%; margin-right:10rem;border-radius:10px" />
<div class="flex items-center justify-between w-[20%]">
<SkeletonText style="width:2%; margin-right:1rem; border-radius:10px" />
<SkeletonText style="width:2%; margin-right:1rem; border-radius:10px" />
<SkeletonText style="width:2%; border-radius:10px" />
</div>
</div>
<!-- hero -->
<div
class="flex flex-row gap-5 items-start justify-center flex-wrap w-full my-8 rounded-md overflow-hidden"
>
<SkeletonPlaceholder style="width:100%; height:20rem" />
</div>
<!-- cards -->
<div class="flex gap-5 items-center justify-center flex-wrap my-4 rounded-md overflow-hidden">
<CardLoader />
<CardLoader />
<CardLoader />
</div>
</section>
+430 -354
View File
@@ -10,7 +10,7 @@
import Phone from 'carbon-icons-svelte/lib/Phone.svelte';
import Email from 'carbon-icons-svelte/lib/Email.svelte';
import Rocket from 'carbon-icons-svelte/lib/Rocket.svelte';
import PageLoader from './PageLoader.svelte';
import { orgLandingpageValidation } from '$lib/utils/functions/validator';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
@@ -27,8 +27,10 @@
import { validateEmail } from '$lib/utils/functions/validateEmail';
import TextArea from '$lib/components/Form/TextArea.svelte';
import { goto } from '$app/navigation';
import { landingPageSettings } from '$lib/components/Org/Settings/store';
export let orgSiteName = '';
export let org = {};
let email: string | undefined;
let isAdding = false;
@@ -36,6 +38,7 @@
let successContactSaved = false;
let viewAll = false;
let isContactSubmiting = false;
let player;
let contact = {
name: '',
email: '',
@@ -43,36 +46,16 @@
message: ''
};
let contactError = {};
let video =
'https://koxqonvbkeakwvmdegcf.supabase.co/storage/v1/object/public/avatars/course/About%20Best.mp4';
const supabase = getSupabase();
const faqs = [
{
title: 'What sets your bootcamp apart from other online resources?',
content:
'Our bootcamp offers a structured and guided learning experience led by industry professionals who have a deep understanding of current web development trends and technologies. Unlike self-paced online tutorials, our program provides a hands-on, immersive environment with real-time feedback, personalized guidance, and the opportunity to work on practical projects that mirror real-world scenarios, preparing you for a successful career in web development.'
},
{
title: 'Who is this bootcamp suitable for?',
content:
"Our web technologies bootcamp is suitable for individuals of all skill levels, from beginners with no prior coding experience to intermediate learners looking to enhance their web development capabilities. Whether you're a career changer, a recent graduate, or a professional aiming to upskill, our program is tailored to help you succeed."
},
{
title: 'How is the bootcamp structured?',
content:
"Our web technologies bootcamp typically spans a [X]-week period and is divided into modules that cover different aspects of web development. Each module consists of lectures, hands-on coding exercises, projects, and collaborative activities. Our experienced instructors provide guidance and support, and you'll have access to a community of fellow learners to foster collaboration and knowledge sharing."
}
];
async function handleSubmit() {
if (!email || !validateEmail(email)) return;
isAdding = true;
const { error } = await supabase.from('organization_emaillist').insert({
email,
organization_id: $currentOrg.id
organization_id: org.id
});
if (error) {
@@ -91,6 +74,7 @@
isContactSubmiting = true;
contactError = orgLandingpageValidation(contact);
if (Object.keys(contactError).length) {
isContactSubmiting = false;
return;
}
@@ -100,11 +84,12 @@
email: contact.email,
phone: contact.phone,
message: contact.message,
organization_id: $currentOrg.id
organization_id: org.id
});
if (error) {
console.error('Something went wrong', error, '\n\nContact', contact);
isContactSubmiting = false;
}
isContactSubmiting = false;
@@ -116,10 +101,15 @@
message: ''
};
}
function isYouTubeLink(link: string) {
const youtubeRegex =
/^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
return youtubeRegex.test(link.trim());
}
onMount(async () => {
if (!orgSiteName) return;
try {
console.log('sitename', orgSiteName);
$courseMetaDeta.isLoading = true;
@@ -130,356 +120,442 @@
console.log('error', error);
}
});
function initPlyr(_player: any, _video: string | undefined) {
if (!player) return;
// @ts-ignore
const plyr = new Plyr('#player', { width: '400px', height: '300px', borderRadius: '10px' });
// @ts-ignore
window.player = plyr;
}
// 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;
}
}
$: initPlyr(player, $landingPageSettings.header.video.link);
$: setDefault(org.landingpage);
</script>
<svelte:head>
<title>
{$currentOrg.name}'s Landing Page
{!org.name ? '' : `${org.name}'s `}Landing Page
</title>
</svelte:head>
<main>
<!-- Header Section -->
<header id="header" class="banner w-full h-[80vh] mb-10 relative">
<Navigation logo={$currentOrg.avatar_url} orgName={$currentOrg.name} disableSignup={true} />
<div class="absolute h-[80vh] top-0 w-full opacity-80 z-10 bg-white" />
{#if !org.landingpage}
<PageLoader />
{:else}
<main>
<!-- Header Section -->
{#if $landingPageSettings.header.show}
<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}
<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"
>
<!-- Course Description -->
<div class="md:w-2/5 w-11/12 py-10 flex flex-col items-center md:items-start mr-3">
<p class=" text-primary-600 text-2xl font-semibold capitalize">
{org.name}
</p>
<h1
class="text-4xl md:text-5xl lg:text-6xl text-center md:text-start my-4 font-bold"
>
{$landingPageSettings.header.title} <br /><span class="text-primary-600"
>{$landingPageSettings.header.titleHighlight}</span
>
</h1>
<p class="text-md mb-3 md:mb-5 text-xl text-center md:text-start">
{$landingPageSettings.header.subtitle}
</p>
{#if video}
<div class="flex items-center justify-center h-full">
<div
class="md:w-11/12 lg:w-5/6 w-full flex items-center justify-between flex-col-reverse md:flex-row z-20 relative"
>
<!-- Course Description -->
<div class="md:w-2/5 w-11/12 py-10 flex flex-col items-start mr-3">
<p class=" text-primary-600 text-2xl font-semibold">Coding Dojo</p>
<h1 class="text-4xl md:text-5xl lg:text-6xl my-4 font-bold">
Web Developement Courses for <br /><span class="text-primary-600">Everyone</span>
</h1>
<p class="text-md mb-6 text-xl">
Practical project-based courses that are easy to understand and straight to the point
with NO FLUFF
</p>
<PrimaryButton
label="Start Learning Today"
className="mt-5 px-10 w-fit"
onClick={() => goto('#contact')}
/>
</div>
<div class="w-4/6 md:w-1/2 md:max-w-[650px] flex">
<video class="w-full rounded-xl" controls loop autoplay>
<source src={video} type="video/mp4" />
<!-- <source src="/path/to/video.webm" type="video/webm" /> -->
<!-- Captions are optional -->
<track kind="captions" />
</video>
</div>
</div>
</div>
{:else}
<div class="w-full h-full flex items-center justify-center md:flex-row z-20 relative">
<div class="max-w-[600px] mx-auto w-11/12 py-10 flex flex-col items-center">
<p class=" text-primary-600 text-2xl font-semibold">Coding Dojo</p>
<h1 class="text-4xl md:text-6xl text-center my-4 font-bold">
Web Developement Courses for <br /><span class="text-primary-600">Everyone</span>
</h1>
<p class="text-md mb-6 text-center text-xl">
Practical project-based courses that are easy to understand and straight to the point
with NO FLUFF
</p>
<PrimaryButton
label="Start Learning Today"
className="mt-5 px-10 w-fit"
onClick={() => goto('#contact')}
/>
</div>
</div>
{/if}
</header>
<!-- Our Story section -->
<section id="aboutus" class="m-h-[400px] my-10 mx-auto max-w-6xl w-full">
<div class="mx-4 flex items-center justify-between flex-col lg:flex-row">
<div class="max-w-[600px] lg:w-2/5 mr-5 mb-5 lg:mb-0">
<h2 class="text-6xl">Our Story</h2>
<p class="mb-2">
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! <br /> Lorem 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.
</p>
</div>
<div class="image">
<img
src="images/org-landingpage-our-story.jpeg"
alt="Our Story"
class=" rounded-2xl w-[600px]"
/>
</div>
</div>
</section>
<!-- Courses Section -->
<section id="courses" transition:fade class="my-10 mx-auto max-w-6xl w-full">
<div class="w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-5xl text-center my-4 font-bold">
Explore our <span class="text-primary-600">Courses</span>
</h1>
<p class="text-md text-center">
Find courses you will love from best teachers all over the world🌎.
</p>
</div>
</div>
<div class="flex gap-5 items-center justify-center flex-wrap my-4 mx-2">
{#if $courseMetaDeta.isLoading}
<CardLoader />
<CardLoader />
<CardLoader />
{:else}
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<Card
id={courseData.id}
slug={courseData.slug}
bannerImage={courseData.banner_image}
title={courseData.title}
description={courseData.description}
role_id={courseData.role_id}
isPublished={courseData.is_published}
cost={courseData.cost}
currency={courseData.currency}
totalLessons={get(courseData, 'lessons[0].count', 0)}
isOnLandingPage={true}
/>
{:else}
<Box>
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Course Published</h3>
<p class="dark:text-white w-1/3 text-center">
We've got great courses coming your way, stay tuned!!!
</p>
</Box>
{/each}
{/if}
</div>
{#if $courses.length > 3}
<div class="w-full flex justify-center">
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={() => (viewAll = !viewAll)}
label={viewAll ? 'View Less' : 'View All'}
className="px-10 py-5 w-fit"
/>
</div>
{/if}
</section>
<!-- FAQ Section -->
<section id="faq" transition:fade class="my-10 mx-auto max-w-[700px] w-full">
<div class="py-10">
<h1 class="text-5xl text-center my-4 font-bold">Frequently Asked Questions.</h1>
</div>
<div class="mx-2">
<Accordion size="xl">
{#each faqs as faq}
<AccordionItem title={faq.title} class="text-3xl">
<p class="text-lg">
{faq.content}
</p>
</AccordionItem>
{/each}
</Accordion>
</div>
</section>
<!-- Contact Section-->
<section id="contact" transition:fade class="my-10 w-full bg-primary-50">
<div class="mx-auto max-w-6xl w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-5xl text-center my-4 font-bold">
Have you got any <span class="text-primary-600">Question?</span>
</h1>
<p class="text-md text-center">Reach out to us with any question you might have.</p>
</div>
<Grid class="max-w-[700px] pb-10">
<!-- Contact Details -->
<Row>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
>
<LocationFilled size={32} />
<p class="text-xs md:text-sm mt-3 max-w-[200px]">
Ashwoodfield House Farm, Kidderminster Rd, Stourbridge, Kingswinford DY6 0AA
</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
>
<Phone size={32} />
<p class="text-xs md:text-sm mt-3">+4407339904995</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
>
<Email size={32} />
<p class="text-xs md:text-sm mt-3">[email protected]</p>
</Column>
</Row>
<!-- Contact Form -->
<div class="mt-8 bg-white p-7 rounded-lg">
{#if successContactSaved}
<div class="w-full flex items-center justify-center">
Thank you for dropping a message, we will get back to you shortly
</div>
{:else}
<form on:submit|preventDefault={handleContactSubmit}>
<div class="w-full flex justify-between flex-col md:flex-row">
<div class="w-full md:w-2/4 mr-5">
<TextField
label="Your Name"
bind:value={contact.name}
errorMessage={contactError.name}
className="mb-5"
labelClassName="font-bold"
placeholder="Elon Musk"
/>
<TextField
label="Your Email"
bind:value={contact.email}
errorMessage={contactError.email}
className="text-xs font-normal mb-5"
placeholder="[email protected]"
/>
<TextField
label="Your Phone"
bind:value={contact.phone}
errorMessage={contactError.phone}
className="text-xs font-normal mb-5"
placeholder="+1194802480"
/>
</div>
<div class="w-full md:w-2/4">
<TextArea
label="Your Message"
bind:value={contact.message}
errorMessage={contactError.message}
rows="9"
maxRows={15}
placeholder="Your message here"
/>
</div>
<PrimaryButton
label={$landingPageSettings.header.action.label}
className="mt-2 md:mt-5 px-10 w-fit"
onClick={() => {
$landingPageSettings.header.action.redirect &&
goto($landingPageSettings.header.action.link);
}}
/>
</div>
<div class="w-5/6 md:w-1/2 md:max-w-[650px] flex">
{#if isYouTubeLink($landingPageSettings.header.video.link)}
<div bind:this={player} id="player" style="width:100%; border-radius:12px">
<iframe
title="Header video"
src={$landingPageSettings.header.video.link}
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>
{/if}
</div>
</div>
</div>
{:else}
<div class="w-full h-full flex items-center justify-center md:flex-row z-20 relative">
<div class="max-w-[600px] mx-auto w-11/12 py-10 flex flex-col items-center">
<p class=" text-primary-600 text-2xl font-semibold capitalize">{org.name}</p>
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
{$landingPageSettings.header.title} <br /><span class="text-primary-600"
>{$landingPageSettings.header.titleHighlight}</span
>
</h1>
<p class="text-md mb-6 text-center text-xl">
{$landingPageSettings.header.subtitle}
</p>
<PrimaryButton
className="w-full mx-auto mt-5 md:mt-0"
type="submit"
isLoading={isContactSubmiting}
>
<span class="mr-2 text-md">Submit</span>
<Rocket size={24} />
</PrimaryButton>
</form>
{/if}
</div>
</Grid>
</div>
</section>
label={$landingPageSettings.header.action.label}
className="mt-5 px-10 w-fit"
onClick={() => {
$landingPageSettings.header.action.redirect &&
goto($landingPageSettings.header.action.link);
}}
/>
</div>
</div>
{/if}
</header>
{/if}
<!-- Waitlist Section -->
<section id="waitlist" transition:fade class="my-10 mx-auto max-w-6xl w-[95%]">
<div
class="bg-primary-700 rounded-lg flex flex-col lg:flex-row lg:items-center px-4 md:px-10 py-14"
>
<div class="w-full md:w-[65%] md:mr-4">
<h1 class="text-4xl font-bold mb-5 mt-0 text-white">Join our mailing list</h1>
<p class="text-lg text-white">
We are constantly releasing new courses and sharing them with our email list. Subscribe to
get notified once we release a new course
</p>
</div>
<form on:submit|preventDefault={handleSubmit} class="my-4 w-full md:w-fit">
<div class="flex items-center flex-col sm:flex-row">
{#if success}
<p class="text-white">You have been added successfully. Thanks for subscribing.</p>
{:else}
<TextField
bind:value={email}
type="email"
placeholder="Enter your email.."
className="sm:mr-3 my-0 w-full md:w-fit"
isRequired={true}
isDisabled={isAdding}
inputClassName="py-2"
<!-- Our Story section -->
{#if $landingPageSettings.aboutUs.show}
<section id="aboutus" class="m-h-[400px] my-10 mx-auto max-w-6xl w-full">
<div class="mx-4 flex items-center justify-evenly flex-col lg:flex-row">
<div class="max-w-[600px] lg:w-2/5 mr-5 mb-5 lg:mb-0">
<h2 class="text-4xl md:text-5xl lg:text-6xl">{$landingPageSettings.aboutUs.title}</h2>
<p class="mb-2">
{$landingPageSettings.aboutUs.content}
</p>
</div>
<div class="image">
<img
src={$landingPageSettings.aboutUs.imageUrl}
alt="Our Story"
class=" rounded-2xl max-h-[450px]"
/>
<PrimaryButton
className="my-1 w-full mt-2"
variant={VARIANTS.CONTAINED_LIGHT}
type="submit"
isLoading={isAdding}>Subscribe</PrimaryButton
>
</div>
</div>
</section>
{/if}
<!-- Courses Section -->
{#if $landingPageSettings.courses.show}
<section id="courses" transition:fade class="my-10 mx-auto max-w-6xl w-full">
<div class="w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
{$landingPageSettings.courses.title}
<span class="text-primary-600">{$landingPageSettings.courses.titleHighlight}</span>
</h1>
<p class="text-md text-center">
{$landingPageSettings.courses.subtitle}
</p>
</div>
</div>
<div class="cards-container my-4 mx-2">
{#if $courseMetaDeta.isLoading}
<CardLoader />
<CardLoader />
<CardLoader />
{:else}
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<Card
id={courseData.id}
slug={courseData.slug}
bannerImage={courseData.banner_image}
title={courseData.title}
description={courseData.description}
role_id={courseData.role_id}
isPublished={courseData.is_published}
cost={courseData.cost}
currency={courseData.currency}
totalLessons={get(courseData, 'lessons[0].count', 0)}
isOnLandingPage={true}
/>
{:else}
<Box>
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Course Published</h3>
<p class="dark:text-white w-1/3 text-center">
We've got great courses coming your way, stay tuned!!!
</p>
</Box>
{/each}
{/if}
</div>
</form>
</div>
</section>
<!-- Footer Section -->
<footer
id="footer"
class="flex justify-center flex-col mt-10 w-full px-5 py-10 md:py-3 border-b-0 border-r-0 border-t border-l-0 border-gray-300"
>
<ul class="flex w-11/12 items-center flex-col sm:flex-row">
<div class="logo">
<a
href="/"
title={`Go to ${$currentOrg.name} Home`}
id="logo"
data-hveid="8"
class="flex items-center"
{#if $courses.length > 3}
<div class="w-full flex justify-center">
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={() => (viewAll = !viewAll)}
label={viewAll ? 'View Less' : 'View All'}
className="px-10 py-5 w-fit"
/>
</div>
{/if}
</section>
{/if}
<!-- FAQ Section -->
{#if $landingPageSettings.faq.show}
<section id="faq" transition:fade class="my-10 mx-auto max-w-[700px] w-full">
<div class="py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
{$landingPageSettings.faq.title}
</h1>
</div>
<div class="mx-2">
<Accordion size="xl">
{#each $landingPageSettings.faq.questions as faq}
<AccordionItem title={faq.title} class="text-3xl">
<p class="text-lg">
{faq.content}
</p>
</AccordionItem>
{/each}
</Accordion>
</div>
</section>
{/if}
<!-- Contact Section-->
{#if $landingPageSettings.contact.show}
<section id="contact" transition:fade class="my-10 w-full bg-primary-50">
<div class="mx-auto max-w-6xl w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
{$landingPageSettings.contact.title}
<span class="text-primary-600">{$landingPageSettings.contact.titleHighlight}</span>
</h1>
<p class="text-md text-center">{$landingPageSettings.contact.subtitle}</p>
</div>
<Grid class="max-w-[700px] pb-10">
<!-- Contact Details -->
<Row>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
>
<LocationFilled size={32} />
<p class="text-xs md:text-sm mt-3 max-w-[200px]">
{$landingPageSettings.contact.address}
</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
>
<Phone size={32} />
<p class="text-xs md:text-sm mt-3">{$landingPageSettings.contact.phone}</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
>
<Email size={32} />
<p class="text-xs md:text-sm mt-3">{$landingPageSettings.contact.email}</p>
</Column>
</Row>
<!-- Contact Form -->
<div class="mt-8 bg-white p-7 rounded-lg">
{#if successContactSaved}
<div class="w-full flex items-center justify-center">
Thank you for dropping a message, we will get back to you shortly
</div>
{:else}
<form on:submit|preventDefault={handleContactSubmit}>
<div class="w-full flex justify-between flex-col md:flex-row">
<div class="w-full md:w-2/4 mr-5">
<TextField
label="Your Name"
bind:value={contact.name}
errorMessage={contactError.name}
className="mb-5"
labelClassName="font-bold"
placeholder="Elon Musk"
/>
<TextField
label="Your Email"
bind:value={contact.email}
errorMessage={contactError.email}
className="text-xs font-normal mb-5"
placeholder="[email protected]"
/>
<TextField
label="Your Phone"
bind:value={contact.phone}
errorMessage={contactError.phone}
className="text-xs font-normal mb-5"
placeholder="+1194802480"
/>
</div>
<div class="w-full md:w-2/4">
<TextArea
label="Your Message"
bind:value={contact.message}
errorMessage={contactError.message}
rows="9"
maxRows={15}
placeholder="Your message here"
/>
</div>
</div>
<PrimaryButton
className="w-full mx-auto mt-5 md:mt-0"
type="submit"
isLoading={isContactSubmiting}
>
<span class="mr-2 text-md">Submit</span>
<Rocket size={24} />
</PrimaryButton>
</form>
{/if}
</div>
</Grid>
</div>
</section>
{/if}
<!-- Waitlist Section -->
{#if $landingPageSettings.mailinglist.show}
<section id="waitlist" transition:fade class="my-10 mx-auto max-w-6xl w-[95%]">
<div
class="bg-primary-700 rounded-lg flex flex-col lg:flex-row lg:items-center px-4 md:px-10 py-14"
>
<img
src={$currentOrg.avatar_url || '/logo-192.png'}
alt={`${$currentOrg.name} logo`}
class="rounded h-10 w-10 inline-block mx-auto"
data-atf="1"
/>
<h3 class="text-black ml-3 text-xl">{$currentOrg.name}</h3>
</a>
</div>
<div class="w-full md:w-[65%] md:mr-4">
<h1 class="text-4xl font-bold mb-5 mt-0 text-white">
{$landingPageSettings.mailinglist.title}
</h1>
<p class="text-lg text-white">
{$landingPageSettings.mailinglist.subtitle}
</p>
</div>
<form on:submit|preventDefault={handleSubmit} class="my-4 w-full md:w-fit">
<div class="flex items-center flex-col sm:flex-row">
{#if success}
<p class="text-white">You have been added successfully. Thanks for subscribing.</p>
{:else}
<TextField
bind:value={email}
type="email"
placeholder="Enter your email.."
className="sm:mr-3 my-0 w-full md:w-fit"
isRequired={true}
isDisabled={isAdding}
inputClassName="py-2"
/>
<PrimaryButton
className="my-1 w-full mt-2"
variant={VARIANTS.CONTAINED_LIGHT}
type="submit"
isLoading={isAdding}>{$landingPageSettings.mailinglist.buttonLabel}</PrimaryButton
>
{/if}
</div>
</form>
</div>
</section>
{/if}
<span class="flex-grow" />
<!-- Footer Section -->
<div class="flex mt-5 sm:mt-0">
<li class="mx-2">
<a href="facebook.com" target="_blank" title="Facebook" id="logo-fb" data-hveid="8">
<LogoFacebook size={24} />
</a>
</li>
<li class="mx-2">
<a href="twitter.com" target="_blank" title="Twitter" id="logo-tw" data-hveid="8">
<LogoTwitter size={24} />
</a>
</li>
<li class="mx-2">
<a href="linkedin.com" target="_blank" title="Linkedin" id="logo-ln" data-hveid="8">
<LogoLinkedin size={24} />
</a>
</li>
</div>
</ul>
<p class="text-center mt-5">
Powered by
<a class="text-primary-600 underline" href="https://classroomio.com" target="_blank"
>ClassroomIO</a
{#if $landingPageSettings.footer.show}
<footer
id="footer"
class="flex justify-center flex-col mt-10 w-full px-5 py-10 md:py-3 border-b-0 border-r-0 border-t border-l-0 border-gray-300"
>
</p>
</footer>
</main>
<ul class="flex w-11/12 items-center flex-col sm:flex-row">
<div class="logo">
<a
href="/"
title={`Go to ${org.name} Home`}
id="logo"
data-hveid="8"
class="flex items-center"
>
<img
src={org.avatar_url || '/logo-192.png'}
alt={`${org.name} logo`}
class="rounded h-10 w-10 inline-block mx-auto"
data-atf="1"
/>
<h3 class="text-black ml-3 text-xl">{org.name}</h3>
</a>
</div>
<span class="flex-grow" />
<div class="flex mt-5 sm:mt-0">
{#if $landingPageSettings.footer.facebook}
<li class="mx-2">
<a
href={$landingPageSettings.footer.facebook}
target="_blank"
title="Facebook"
id="logo-fb"
data-hveid="8"
>
<LogoFacebook size={24} />
</a>
</li>
{/if}
{#if $landingPageSettings.footer.twitter}
<li class="mx-2">
<a
href={$landingPageSettings.footer.twitter}
target="_blank"
title="Twitter"
id="logo-tw"
data-hveid="8"
>
<LogoTwitter size={24} />
</a>
</li>
{/if}
{#if $landingPageSettings.footer.linkedin}
<li class="mx-2">
<a
href={$landingPageSettings.footer.linkedin}
target="_blank"
title="Linkedin"
id="logo-ln"
data-hveid="8"
>
<LogoLinkedin size={24} />
</a>
</li>
{/if}
</div>
</ul>
<p class="text-center mt-5">
Powered by
<a class="text-primary-600 underline" href="https://classroomio.com" target="_blank"
>ClassroomIO</a
>
</p>
</footer>
{/if}
</main>
{/if}
<style>
.banner {
@@ -14,6 +14,7 @@
import { SNACKBAR_SEVERITY } from '$lib/components/Snackbar/constants';
import { FileUploader } from 'carbon-components-svelte';
import { landingPageSettings } from './store';
import type { OrgLandingPageJson } from './store';
let files: File[] = [];
let imageBuffer: File;
@@ -88,9 +89,22 @@
};
};
const checkPrefix = (inputValue: string) => {
if (!inputValue) return;
if (inputValue.trim() !== '') {
if (!inputValue.startsWith('https://')) {
inputValue = 'https://' + inputValue;
}
}
return inputValue;
};
async function handleSave() {
isSaving = true;
$landingPageSettings.footer.twitter = checkPrefix($landingPageSettings.footer.twitter);
$landingPageSettings.footer.linkedin = checkPrefix($landingPageSettings.footer.linkedin);
$landingPageSettings.footer.facebook = checkPrefix($landingPageSettings.footer.facebook);
console.log($landingPageSettings.footer.twitter);
const { data, error } = await supabase
.from('organization')
.update({ landingpage: $landingPageSettings })
@@ -112,13 +126,13 @@
isSaving = false;
}
function setDefault(landingpage) {
function setDefault(landingpage: OrgLandingPageJson) {
if (landingpage && Object.keys(landingpage).length) {
$landingPageSettings = landingpage;
}
}
$: setDefault($currentOrg.landingpage);
$: setDefault($currentOrg?.landingpage);
</script>
<Grid class="border-c rounded border-gray-200 w-full mt-5 relative">
@@ -166,8 +180,8 @@
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>
<span slot="labelA" style="color: gray">No redirect</span>
<span slot="labelB" style="color: gray">Redirect</span>
</Toggle>
</div>
<SectionTitle>Video</SectionTitle>
@@ -178,8 +192,8 @@
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>
<span slot="labelA" style="color: gray">Hide Video</span>
<span slot="labelB" style="color: gray">Show Video</span>
</Toggle>
</div>
</Column>
@@ -408,13 +422,18 @@
bind:value={$landingPageSettings.footer.facebook}
className="mb-5"
/>
<TextField
<!-- <TextField
label="Instagram"
placeholder="Write your Instagram link here"
className="mb-5"
bind:value={$landingPageSettings.footer.instagram}
/> -->
<TextField
label="Twitter"
placeholder="Write your Twitter link here"
className="mb-5"
bind:value={$landingPageSettings.footer.twitter}
/>
<TextField
label="Linkedin"
placeholder="Write your Linkedin link here"
+5 -3
View File
@@ -56,6 +56,7 @@ export interface OrgLandingPageJson {
footer: {
facebook: string;
instagram: string;
twitter: string;
linkedin: string;
show: boolean;
};
@@ -72,8 +73,8 @@ export let landingPageSettings: Writable<OrgLandingPageJson> = writable({
redirect: false
},
video: {
show: false,
link: 'https://youtube.com/sosf24lsfgs'
show: true,
link: 'https://www.youtube.com/watch?v=bbVZo4Yw7pI'
},
show: false
},
@@ -82,7 +83,7 @@ export let landingPageSettings: Writable<OrgLandingPageJson> = writable({
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
show: false
},
courses: {
title: 'Explore our',
@@ -133,6 +134,7 @@ export let landingPageSettings: Writable<OrgLandingPageJson> = writable({
footer: {
facebook: '#',
instagram: '#',
twitter: '#',
linkedin: '#',
show: true
}
+2 -1
View File
@@ -22,7 +22,8 @@ export const PUBLIC_ROUTES = [
ROUTE.FORGOT,
ROUTE.RESET,
`^${ROUTE.PAGES}/.*`,
`^${ROUTE.COURSE}/.*`
`^${ROUTE.COURSE}/.*`,
'/404'
];
export const ROUTES_TO_HIDE_NAV = [
+13
View File
@@ -0,0 +1,13 @@
export function setTheme(theme: string) {
// In case the default theme is added but another theme exists
if (!theme && document.body.className.includes('theme-')) {
const regex = /theme-[\w]+/gi;
document.body.className = document.body.className.replace(regex, theme);
return;
}
// In case theme already exists in dom, don't add
if (document.body.className.includes(theme)) return;
document.body.className = document.body.className.concat(' ', theme);
}
-10
View File
@@ -147,16 +147,6 @@ export async function getCourseBySiteName(siteName) {
if (error) {
return [];
}
const org = get(data, '[0].group.organization', {
id: '',
name: 'ClassroomIO',
shortName: '',
siteName: '',
avatar_url: '/logo-192.png',
landingpage: {},
theme: ''
});
currentOrg.set(org);
return data;
}
+4 -5
View File
@@ -20,10 +20,10 @@ export const load = async ({ url, cookies }) => {
org: {}
};
const debugOrgLandingPage = cookies.get('debugOrgLandingPage');
const _orgSiteName = cookies.get('_orgSiteName');
const debugPlay = cookies.get('debugPlay');
const debugMode = debugOrgLandingPage === 'true';
console.log('debugOrgLandingPage', debugOrgLandingPage);
const debugMode = _orgSiteName !== 'false';
console.log('_orgSiteName', _orgSiteName);
const matches = url.host.match(/([a-z 0-9 \-]+).*classroomio[.]com/);
const subdomain = matches?.[1] ?? '';
@@ -32,8 +32,7 @@ export const load = async ({ url, cookies }) => {
const answer = Array.isArray(matches) ? !!subdomain && subdomain !== 'www' : false;
response.isOrgSite = debugMode || answer;
response.orgSiteName = debugMode ? 'codingdojo' : subdomain;
response.orgSiteName = debugMode ? _orgSiteName : subdomain;
response.org = await getCurrentOrg(response.orgSiteName, true);
} else if (subdomain === 'play' || debugPlay === 'true') {
response.skipAuth = true;
+20 -9
View File
@@ -34,6 +34,7 @@
import { toggleBodyByTheme } from '$lib/utils/functions/app';
import { appStore } from '$lib/utils/store/app';
import { currentOrg } from '$lib/utils/store/org';
import { setTheme } from '$lib/utils/functions/theme';
import '../app.postcss';
@@ -47,10 +48,6 @@
setTimeout(() => set(currentPreloading), 250);
});
function setTheme(theme) {
document.body.className = document.body.className.concat(' ', theme);
}
function setupSentry() {
if (!dev) {
Sentry.init({
@@ -243,11 +240,15 @@
if (data.isOrgSite) {
console.log('data', data);
$appStore.orgSiteName = data.orgSiteName;
$appStore.isOrgSite = data.isOrgSite;
if (!data.org) {
goto('/404');
} else {
$appStore.orgSiteName = data.orgSiteName;
$appStore.isOrgSite = data.isOrgSite;
currentOrg.set(data.org);
setTheme(data.org.theme);
currentOrg.set(data.org);
setTheme(data.org.theme);
}
}
return () => {
@@ -273,7 +274,7 @@
{#if data.skipAuth}
<PlayQuiz />
{:else if data.isOrgSite && !path}
<OrgLandingPage orgSiteName={data.orgSiteName} />
<OrgLandingPage orgSiteName={data.orgSiteName} org={data.org} />
{:else}
<main class="dark:bg-gray-800">
{#if $navigating && $delayedPreloading}
@@ -390,4 +391,14 @@
linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.5)) !important;
background-size: 50px auto, auto !important;
}
:global(.cards-container) {
width: 90%;
margin: 0 auto;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
column-gap: 12px;
row-gap: 12px;
}
</style>