Org landing page template

This commit is contained in:
Rotimi Best
2023-08-16 04:24:24 +01:00
parent 2ce3063b83
commit ad2b87ca15
7 changed files with 362 additions and 70 deletions
+4 -13
View File
@@ -10,6 +10,7 @@
export let logo;
export let orgName;
export let isOrgSite = false;
export let backgroundColor = 'bg-blue';
let navClass = '';
@@ -19,7 +20,7 @@
</script>
<nav
class="{navClass} bg-white sticky top-0 z-50 flex w-full p-2 border-t-0 border-r-0 border-b border-l-0 border-gray-300"
class="{navClass} {backgroundColor} sticky top-0 z-50 flex w-full px-2 py-1 border-t-0 border-r-0 border-b border-l-0 border-gray-300"
>
<ul class="flex w-full items-center">
<div class="logo">
@@ -27,7 +28,7 @@
<img
src={logo || '/logo-192.png'}
alt={`${orgName || 'ClassroomIO'} logo`}
class="rounded h-10 w-10 inline-block mx-auto"
class="rounded h-9 w-9 inline-block mx-auto"
data-atf="1"
/>
</a>
@@ -63,19 +64,9 @@
<div class="flex">
<PrimaryButton
label="Login"
variant={VARIANTS.OUTLINED}
className="mx-2"
variant={VARIANTS.TEXT}
onClick={() => goto('/login' + redirect)}
/>
<PrimaryButton
label="Sign Up"
variant={VARIANTS.CONTAINED}
className="mx-2"
onClick={() => {
console.log('clicked signup');
goto('/signup' + redirect);
}}
/>
</div>
{/if}
</li>
@@ -0,0 +1,21 @@
<script>
import { Search } from 'carbon-components-svelte';
let searchValue = '';
</script>
<section class="bg-[#040f2d] min-h-[337px] w-full flex items-center justify-center p-">
<div class="w-full flex items-center justify-center flex-col-reverse md:flex-row">
<!-- Course Description -->
<div class="course-descr w-11/12 py-10">
<h1 class="dark:text-white text-5xl text-white text-center my-4 font-bold">
Explore courses
</h1>
<p class="dark:text-white text-md text-white mb-6 text-center">
Find courses you will love from best teachers all over the world🌎.
</p>
<Search placeholder="Find a course..." bind:value={searchValue} />
</div>
</div>
</section>
+290 -33
View File
@@ -2,11 +2,16 @@
import { fade } from 'svelte/transition';
import get from 'lodash/get';
import { onMount } from 'svelte';
import { Search } from 'carbon-components-svelte';
import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte';
import LogoTwitter from 'carbon-icons-svelte/lib/LogoTwitter.svelte';
import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte';
import { Accordion, AccordionItem, Grid, Row, Column } from 'carbon-components-svelte';
import LocationFilled from 'carbon-icons-svelte/lib/LocationFilled.svelte';
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 { orgLandingpageValidation } from '$lib/utils/functions/validator';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
@@ -20,23 +25,52 @@
import { getCourseBySiteName } from '$lib/utils/services/org';
import { currentOrg } from '$lib/utils/store/org';
import { validateEmail } from '$lib/utils/functions/validateEmail';
import TextArea from '$lib/components/Form/TextArea.svelte';
import { goto } from '$app/navigation';
export let orgSiteName;
export let orgSiteName = '';
let searchValue = '';
let email: string | undefined;
let isAdding = false;
let success = false;
let successContactSaved = false;
let viewAll = false;
let isContactSubmiting = false;
let contact = {
name: '',
email: '',
phone: '',
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, data } = await supabase.from('organization_emaillist').insert({
const { error } = await supabase.from('organization_emaillist').insert({
email,
organization_id: $currentOrg.id
});
@@ -53,6 +87,36 @@
}, 5000);
}
async function handleContactSubmit() {
isContactSubmiting = true;
contactError = orgLandingpageValidation(contact);
if (Object.keys(contactError).length) {
return;
}
// Save to db
const { error } = await supabase.from('organization_contacts').insert({
name: contact.name,
email: contact.email,
phone: contact.phone,
message: contact.message,
organization_id: $currentOrg.id
});
if (error) {
console.error('Something went wrong', error, '\n\nContact', contact);
}
isContactSubmiting = false;
successContactSaved = true;
contact = {
name: '',
email: '',
phone: '',
message: ''
};
}
onMount(async () => {
if (!orgSiteName) return;
@@ -73,29 +137,102 @@
</svelte:head>
<main>
<Navigation logo={$currentOrg.avatar_url} orgName={$currentOrg.name} />
<!-- Header Section -->
<header transition:fade class="banner w-full flex items-center justify-center p-">
<div class="w-full flex items-center justify-center flex-col-reverse md:flex-row">
<!-- Course Description -->
<div class="course-descr w-11/12 py-10">
<h1 class="dark:text-white text-5xl text-white text-center my-4 font-bold">
Explore courses
</h1>
<p class="dark:text-white text-md text-white mb-6 text-center">
Find courses you will love from best teachers all over the world🌎.
</p>
<header id="header" class="banner w-full h-[80vh] mb-10 relative">
<Navigation logo={$currentOrg.avatar_url} orgName={$currentOrg.name} disableLogin={false} />
<div class="absolute h-[80vh] top-0 w-full opacity-80 z-10 bg-white" />
<Search placeholder="Find a course..." bind:value={searchValue} />
{#if video}
<div class="flex items-center justify-center">
<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">
<p class=" text-blue-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-blue-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-2/3 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>
</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-blue-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-blue-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>
<section transition:fade class="my-10 mx-auto max-w-6xl w-full">
<div
class="flex gap-5 items-center {$courses.length < 4 && 'justify-center'} flex-wrap my-4 mx-2"
>
<!-- 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-blue-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 />
@@ -139,7 +276,120 @@
{/if}
</section>
<section transition:fade class="my-10 mx-auto max-w-6xl w-[95%]">
<!-- 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-blue-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-blue-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>
</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>
<!-- Waitlist Section -->
<section id="waitlist" transition:fade class="my-10 mx-auto max-w-6xl w-[95%]">
<div
class="bg-blue-700 rounded-lg flex flex-col lg:flex-row lg:items-center px-4 md:px-10 py-14"
>
@@ -159,13 +409,13 @@
bind:value={email}
type="email"
placeholder="Enter your email.."
className="md:mr-3 my-0 w-full md:w-fit"
className="sm:mr-3 my-0 w-full md:w-fit"
isRequired={true}
isDisabled={isAdding}
inputClassName="py-2"
/>
<PrimaryButton
className="my-1 w-full"
className="my-1 w-full mt-2"
variant={VARIANTS.CONTAINED_LIGHT}
type="submit"
isLoading={isAdding}>Subscribe</PrimaryButton
@@ -176,8 +426,10 @@
</div>
</section>
<!-- Footer Section -->
<footer
class="flex justify-center 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"
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">
@@ -218,16 +470,21 @@
</li>
</div>
</ul>
<p class="text-center mt-5">
Powered by
<a class="text-blue-600 underline" href="https://classroomio.com" target="_blank"
>ClassroomIO</a
>
</p>
</footer>
</main>
<style lang="scss">
<style>
.banner {
background-color: #040f2d;
min-height: 337px;
}
.course-descr {
max-width: 400px;
background: url('/images/org-landingpage-banner.jpeg') no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
</style>
+46 -23
View File
@@ -6,72 +6,91 @@ import { validateEmail } from './validateEmail';
const createQuizValidationSchema = z.object({
title: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty',
}),
invalid_type_error: 'Must not be empty'
})
});
const askCommunityValidationSchema = z.object({
title: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty',
invalid_type_error: 'Must not be empty'
}),
body: z.string().min(20, {
message: 'Must be 10 or more characters long',
invalid_type_error: 'Must not be empty',
}),
invalid_type_error: 'Must not be empty'
})
});
const commentInCommunityValidationSchema = z.object({
comment: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty',
invalid_type_error: 'Must not be empty'
})
});
const orgLandingpageValidationSchema = z.object({
name: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
}),
email: z.string().email({
message: 'Invalid email address',
invalid_type_error: 'Must not be empty'
}),
phone: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
}),
message: z.string().min(20, {
message: 'Must be 10 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const forgotValidationSchema = z.object({
email: z.string().email({
message: 'Invalid email address',
invalid_type_error: 'Must not be empty',
}),
invalid_type_error: 'Must not be empty'
})
});
const authValidationSchema = z.object({
email: z.string().email({
message: 'Invalid email address',
invalid_type_error: 'Must not be empty',
invalid_type_error: 'Must not be empty'
}),
password: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty',
}),
invalid_type_error: 'Must not be empty'
})
});
const resetValidationSchema = z.object({
password: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty',
}),
invalid_type_error: 'Must not be empty'
})
});
const onboardingValidationSchema = {
stepOne: z.object({
fullname: z.string().min(1, { message: 'Enter your fullname' }),
orgName: z.string().min(1, { message: 'Enter your organisation name' }),
siteName: z.string().min(1, { message: 'Enter your site name' }),
siteName: z.string().min(1, { message: 'Enter your site name' })
}),
stepTwo: z.object({
goal: z
.string({
required_error: 'Select an option',
invalid_type_error: 'Select an option',
invalid_type_error: 'Select an option'
})
.min(1),
source: z
.string({
required_error: 'Select an option',
invalid_type_error: 'Select an option',
invalid_type_error: 'Select an option'
})
.min(1),
}),
.min(1)
})
};
export const getConfirmPasswordError = ({ password, confirmPassword }) => {
@@ -128,11 +147,15 @@ export const forgotValidation = (fields = {}) => {
return processErrors(error);
};
export const orgLandingpageValidation = (fields = {}) => {
const { error } = orgLandingpageValidationSchema.safeParse(fields);
return processErrors(error);
};
export const onboardingValidation = (fields = {}, step) => {
const schema =
step === 1
? onboardingValidationSchema.stepOne
: onboardingValidationSchema.stepTwo;
step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
const { error } = schema.safeParse(fields);
return processErrors(error);
@@ -168,7 +191,7 @@ export const validateEmailInString = (emailsStr) => {
return {
hasError: true,
error: 'Enter an email',
emails: [],
emails: []
};
}
@@ -186,6 +209,6 @@ export const validateEmailInString = (emailsStr) => {
return {
hasError,
error,
emails,
emails
};
};
+1 -1
View File
@@ -18,7 +18,7 @@ export const load = ({ url, cookies }) => {
const debugMode = debugOrgLandingPage === 'true';
console.log('debugOrgLandingPage', debugOrgLandingPage);
const matches = url.host.match(/([a-z0-9]+).*classroomio[.]com/);
const matches = url.host.match(/([a-z 0-9 \-]+).*classroomio[.]com/);
const subdomain = matches?.[1] ?? '';
if (!blockedSubdomain.includes(subdomain)) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB