first commit

This commit is contained in:
Rotimi Best
2023-08-14 06:22:03 +01:00
parent 0ba6e92b0b
commit 2ce3063b83
10 changed files with 185 additions and 98 deletions
+2 -2
View File
@@ -2,7 +2,6 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { user } from '$lib/utils/store/user'; import { user } from '$lib/utils/store/user';
import { appStore } from '$lib/utils/store/app';
import { isCoursePage } from '$lib/utils/functions/app'; import { isCoursePage } from '$lib/utils/functions/app';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte'; import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants'; import { VARIANTS } from '$lib/components/PrimaryButton/constants';
@@ -10,6 +9,7 @@
export let disableLogin; export let disableLogin;
export let logo; export let logo;
export let orgName; export let orgName;
export let isOrgSite = false;
let navClass = ''; let navClass = '';
@@ -54,7 +54,7 @@
<span class="flex-grow" /> <span class="flex-grow" />
{#if $user.isLoggedIn} {#if $user.isLoggedIn}
{#if $appStore.isStudentDomain} {#if isOrgSite}
<li><a class="block" href="/lms"> Go to LMS </a></li> <li><a class="block" href="/lms"> Go to LMS </a></li>
{/if} {/if}
{:else} {:else}
+115 -44
View File
@@ -1,46 +1,86 @@
<script> <script lang="ts">
import { fade } from 'svelte/transition';
import get from 'lodash/get';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { Search } from 'carbon-components-svelte'; import { Search } from 'carbon-components-svelte';
import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte'; import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte';
import LogoTwitter from 'carbon-icons-svelte/lib/LogoTwitter.svelte'; import LogoTwitter from 'carbon-icons-svelte/lib/LogoTwitter.svelte';
import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte'; import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import Navigation from '$lib/components/Navigation/index.svelte'; import Navigation from '$lib/components/Navigation/index.svelte';
import Box from '$lib/components/Box/index.svelte'; import Box from '$lib/components/Box/index.svelte';
import Card from '$lib/components/Courses/components/Card/index.svelte'; import Card from '$lib/components/Courses/components/Card/index.svelte';
import CardLoader from '$lib/components/Courses/components/Card/Loader.svelte'; import CardLoader from '$lib/components/Courses/components/Card/Loader.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte'; import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import { getSupabase } from '$lib/utils/functions/supabase';
import { courses, courseMetaDeta } from '$lib/components/Courses/store'; import { courses, courseMetaDeta } from '$lib/components/Courses/store';
import { getCourseBySiteName } from '$lib/utils/services/org'; import { getCourseBySiteName } from '$lib/utils/services/org';
import { appStore } from '$lib/utils/store/app';
import { currentOrg } from '$lib/utils/store/org'; import { currentOrg } from '$lib/utils/store/org';
import { validateEmail } from '$lib/utils/functions/validateEmail';
import { goto } from '$app/navigation';
export let orgSiteName;
let searchValue = ''; let searchValue = '';
let email: string | undefined;
let isAdding = false;
let success = false;
let viewAll = false;
const supabase = getSupabase();
async function handleSubmit() {
if (!email || !validateEmail(email)) return;
isAdding = true;
const { error, data } = await supabase.from('organization_emaillist').insert({
email,
organization_id: $currentOrg.id
});
if (error) {
console.error('Error', error);
}
success = true;
setTimeout(() => {
isAdding = false;
success = false;
email = undefined;
}, 5000);
}
onMount(async () => { onMount(async () => {
console.log('sitename', $appStore.siteNameFromDomain); if (!orgSiteName) return;
$courseMetaDeta.isLoading = true;
const coursesResult = await getCourseBySiteName( try {
$appStore.siteNameFromDomain console.log('sitename', orgSiteName);
); $courseMetaDeta.isLoading = true;
courses.set(coursesResult); const coursesResult = await getCourseBySiteName(orgSiteName);
$courseMetaDeta.isLoading = false; courses.set(coursesResult);
$courseMetaDeta.isLoading = false;
} catch (error) {
console.log('error', error);
}
}); });
</script> </script>
<svelte:head>
{$currentOrg.name}
</svelte:head>
<main> <main>
<Navigation logo={$currentOrg.avatar_url} orgName={$currentOrg.name} /> <Navigation logo={$currentOrg.avatar_url} orgName={$currentOrg.name} />
<!-- Header Section --> <!-- Header Section -->
<header class="banner w-full flex items-center justify-center p-"> <header transition:fade class="banner w-full flex items-center justify-center p-">
<div <div class="w-full flex items-center justify-center flex-col-reverse md:flex-row">
class="w-full flex items-center justify-center flex-col-reverse md:flex-row"
>
<!-- Course Description --> <!-- Course Description -->
<div class="course-descr w-11/12 py-10"> <div class="course-descr w-11/12 py-10">
<h1 <h1 class="dark:text-white text-5xl text-white text-center my-4 font-bold">
class="dark:text-white text-5xl text-white text-center my-4 font-bold"
>
Explore courses Explore courses
</h1> </h1>
<p class="dark:text-white text-md text-white mb-6 text-center"> <p class="dark:text-white text-md text-white mb-6 text-center">
@@ -52,18 +92,19 @@
</div> </div>
</header> </header>
<section class="my-10 mx-auto max-w-6xl w-full"> <section transition:fade class="my-10 mx-auto max-w-6xl w-full">
<div class="flex items-center justify-evenly flex-wrap my-4 m-auto"> <div
class="flex gap-5 items-center {$courses.length < 4 && 'justify-center'} flex-wrap my-4 mx-2"
>
{#if $courseMetaDeta.isLoading} {#if $courseMetaDeta.isLoading}
<CardLoader /> <CardLoader />
<CardLoader /> <CardLoader />
<CardLoader /> <CardLoader />
{:else} {:else}
{#each $courses as courseData} {#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<Card <Card
id={courseData.id} id={courseData.id}
slug={courseData.slug} slug={courseData.slug}
logo={courseData.logo}
bannerImage={courseData.banner_image} bannerImage={courseData.banner_image}
title={courseData.title} title={courseData.title}
description={courseData.description} description={courseData.description}
@@ -71,7 +112,7 @@
isPublished={courseData.is_published} isPublished={courseData.is_published}
cost={courseData.cost} cost={courseData.cost}
currency={courseData.currency} currency={courseData.currency}
totalLessons={courseData.total_lessons} totalLessons={get(courseData, 'lessons[0].count', 0)}
isOnLandingPage={true} isOnLandingPage={true}
/> />
{:else} {:else}
@@ -85,10 +126,58 @@
{/each} {/each}
{/if} {/if}
</div> </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>
<section 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"
>
<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="md:mr-3 my-0 w-full md:w-fit"
isRequired={true}
isDisabled={isAdding}
inputClassName="py-2"
/>
<PrimaryButton
className="my-1 w-full"
variant={VARIANTS.CONTAINED_LIGHT}
type="submit"
isLoading={isAdding}>Subscribe</PrimaryButton
>
{/if}
</div>
</form>
</div>
</section> </section>
<footer <footer
class="flex justify-center mt-10 w-full px-5 py-5 sm:py-10 border-b-0 border-r-0 border-t border-l-0 border-gray-300" 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"
> >
<ul class="flex w-11/12 items-center flex-col sm:flex-row"> <ul class="flex w-11/12 items-center flex-col sm:flex-row">
<div class="logo"> <div class="logo">
@@ -105,7 +194,7 @@
class="rounded h-10 w-10 inline-block mx-auto" class="rounded h-10 w-10 inline-block mx-auto"
data-atf="1" data-atf="1"
/> />
<h3 class="text-black ml-3">{$currentOrg.name}</h3> <h3 class="text-black ml-3 text-xl">{$currentOrg.name}</h3>
</a> </a>
</div> </div>
@@ -113,35 +202,17 @@
<div class="flex mt-5 sm:mt-0"> <div class="flex mt-5 sm:mt-0">
<li class="mx-2"> <li class="mx-2">
<a <a href="facebook.com" target="_blank" title="Facebook" id="logo-fb" data-hveid="8">
href="facebook.com"
target="_blank"
title="Facebook"
id="logo-fb"
data-hveid="8"
>
<LogoFacebook size={24} /> <LogoFacebook size={24} />
</a> </a>
</li> </li>
<li class="mx-2"> <li class="mx-2">
<a <a href="twitter.com" target="_blank" title="Twitter" id="logo-tw" data-hveid="8">
href="twitter.com"
target="_blank"
title="Twitter"
id="logo-tw"
data-hveid="8"
>
<LogoTwitter size={24} /> <LogoTwitter size={24} />
</a> </a>
</li> </li>
<li class="mx-2"> <li class="mx-2">
<a <a href="linkedin.com" target="_blank" title="Linkedin" id="logo-ln" data-hveid="8">
href="linkedin.com"
target="_blank"
title="Linkedin"
id="logo-ln"
data-hveid="8"
>
<LogoLinkedin size={24} /> <LogoLinkedin size={24} />
</a> </a>
</li> </li>
@@ -1,5 +1,6 @@
export const VARIANTS = { export const VARIANTS = {
CONTAINED: 'CONTAINED', CONTAINED: 'CONTAINED',
CONTAINED_LIGHT: 'CONTAINED_LIGHT',
CONTAINED_INFO: 'CONTAINED_INFO', CONTAINED_INFO: 'CONTAINED_INFO',
CONTAINED_WHITE: 'CONTAINED_WHITE', CONTAINED_WHITE: 'CONTAINED_WHITE',
CONTAINED_DANGER: 'CONTAINED_DANGER', CONTAINED_DANGER: 'CONTAINED_DANGER',
@@ -12,6 +13,7 @@ export const VARIANTS = {
export const VARIANTS_CLASS = { export const VARIANTS_CLASS = {
[VARIANTS.CONTAINED]: 'border-none bg-blue-700 hover:bg-blue-900 text-white font-bold', [VARIANTS.CONTAINED]: 'border-none bg-blue-700 hover:bg-blue-900 text-white font-bold',
[VARIANTS.CONTAINED_LIGHT]: 'border-none bg-blue-600 hover:bg-blue-900 text-white font-bold',
[VARIANTS.CONTAINED_WHITE]: [VARIANTS.CONTAINED_WHITE]:
'border-none hover:border-black-300 hover:bg-blue-700 dark:hover:bg-blue-900 text-black hover:text-white bg-white', 'border-none hover:border-black-300 hover:bg-blue-700 dark:hover:bg-blue-900 text-black hover:text-white bg-white',
[VARIANTS.CONTAINED_INFO]: 'border-none bg-gray-400 hover:bg-gray-600 text-white', [VARIANTS.CONTAINED_INFO]: 'border-none bg-gray-400 hover:bg-gray-600 text-white',
+5 -3
View File
@@ -3,6 +3,7 @@ export const ROUTE = {
DASHBOARD: '/dashboard', DASHBOARD: '/dashboard',
ONBOARDING: '/onboarding', ONBOARDING: '/onboarding',
COURSES: '/courses', COURSES: '/courses',
PAGES: '/pages',
COURSE: '/course', COURSE: '/course',
LOGIN: '/login', LOGIN: '/login',
SIGN_UP: '/signup', SIGN_UP: '/signup',
@@ -11,7 +12,7 @@ export const ROUTE = {
DISCUSSIONS: '/discussions', DISCUSSIONS: '/discussions',
ASK: '/ask', ASK: '/ask',
FORGOT: '/forgot', FORGOT: '/forgot',
RESET: '/reset', RESET: '/reset'
}; };
export const PUBLIC_ROUTES = [ export const PUBLIC_ROUTES = [
@@ -20,7 +21,8 @@ export const PUBLIC_ROUTES = [
ROUTE.SIGN_UP, ROUTE.SIGN_UP,
ROUTE.FORGOT, ROUTE.FORGOT,
ROUTE.RESET, ROUTE.RESET,
`^${ROUTE.COURSE}/.*`, `^${ROUTE.PAGES}/.*`,
`^${ROUTE.COURSE}/.*`
]; ];
export const ROUTES_TO_HIDE_NAV = [ export const ROUTES_TO_HIDE_NAV = [
@@ -28,5 +30,5 @@ export const ROUTES_TO_HIDE_NAV = [
ROUTE.SIGN_UP, ROUTE.SIGN_UP,
ROUTE.FORGOT, ROUTE.FORGOT,
ROUTE.RESET, ROUTE.RESET,
ROUTE.ONBOARDING, ROUTE.ONBOARDING
]; ];
+9 -9
View File
@@ -34,7 +34,7 @@ export async function getOrgTeam(orgId) {
verified: teamMember?.verified, verified: teamMember?.verified,
profileId: teamMember?.profile?.id, profileId: teamMember?.profile?.id,
role: ROLE_LABEL[teamMember?.role_id] || '', role: ROLE_LABEL[teamMember?.role_id] || '',
isAdmin: teamMember?.role_id === ROLE.ADMIN, isAdmin: teamMember?.role_id === ROLE.ADMIN
}); });
}); });
@@ -43,7 +43,7 @@ export async function getOrgTeam(orgId) {
return { return {
team, team,
error, error
}; };
} }
@@ -70,11 +70,10 @@ export async function getOrganizations(userId) {
orgsArray.push({ orgsArray.push({
id: orgMember?.organization?.id, id: orgMember?.organization?.id,
name: orgMember?.organization?.name, name: orgMember?.organization?.name,
shortName: shortName: orgMember?.organization?.name?.substring(0, 2)?.toUpperCase() || '',
orgMember?.organization?.name?.substring(0, 2)?.toUpperCase() || '',
siteName: orgMember?.organization?.siteName, siteName: orgMember?.organization?.siteName,
avatar_url: orgMember?.organization?.avatar_url, avatar_url: orgMember?.organization?.avatar_url,
memberId: orgMember?.id, memberId: orgMember?.id
}); });
}); });
@@ -85,7 +84,7 @@ export async function getOrganizations(userId) {
return { return {
orgs: orgsArray, orgs: orgsArray,
currentOrg: orgsArray[0], currentOrg: orgsArray[0],
error, error
}; };
} }
@@ -116,13 +115,13 @@ export async function getOrgAudience(orgId) {
name: profile.fullname, name: profile.fullname,
email: profile.email, email: profile.email,
avatar_url: profile.avatar_url, avatar_url: profile.avatar_url,
date_joined: new Date(profile.created_at).toDateString(), date_joined: new Date(profile.created_at).toDateString()
})); }));
orgAudience.set(audience); orgAudience.set(audience);
return { return {
audience: audience, audience: audience,
error, error
}; };
} }
@@ -132,6 +131,7 @@ export async function getCourseBySiteName(siteName) {
.select( .select(
` `
*, *,
lessons:lesson(count),
group!inner( group!inner(
organization!inner(id, name, siteName, avatar_url) organization!inner(id, name, siteName, avatar_url)
) )
@@ -148,7 +148,7 @@ export async function getCourseBySiteName(siteName) {
name: 'ClassroomIO', name: 'ClassroomIO',
shortName: '', shortName: '',
siteName: '', siteName: '',
avatar_url: '/logo-192.png', avatar_url: '/logo-192.png'
}); });
currentOrg.set(org); currentOrg.set(org);
+2 -2
View File
@@ -2,6 +2,6 @@ import { writable } from 'svelte/store';
export const appStore = writable({ export const appStore = writable({
isDark: false, isDark: false,
isStudentDomain: false, isOrgSite: false,
siteNameFromDomain: '', orgSiteName: ''
}); });
-8
View File
@@ -1,8 +0,0 @@
import { redirect } from '@sveltejs/kit';
export const load = ({ url }) => {
// if (url.hostname === 'classroomio.com') {
// throw redirect(301, 'https://about.classroomio.com');
// }
return {}
};
+34
View File
@@ -0,0 +1,34 @@
// import { redirect } from '@sveltejs/kit';
import { blockedSubdomain } from '$lib/utils/constants/app';
/** @type {import('./$types').LayoutServerLoad} */
export const load = ({ url, cookies }) => {
// if (url.hostname === 'classroomio.com') {
// throw redirect(301, 'https://about.classroomio.com');
// }
let response = {
orgSiteName: '',
isOrgSite: false,
skipAuth: false
};
const debugOrgLandingPage = cookies.get('debugOrgLandingPage');
const debugPlay = cookies.get('debugPlay');
const debugMode = debugOrgLandingPage === 'true';
console.log('debugOrgLandingPage', debugOrgLandingPage);
const matches = url.host.match(/([a-z0-9]+).*classroomio[.]com/);
const subdomain = matches?.[1] ?? '';
if (!blockedSubdomain.includes(subdomain)) {
const answer = Array.isArray(matches) ? !!subdomain && subdomain !== 'www' : false;
response.isOrgSite = debugMode || answer;
response.orgSiteName = debugMode ? 'codingdojo' : subdomain;
} else if (subdomain === 'play' || debugPlay === 'true') {
response.skipAuth = true;
}
return response;
};
+16 -30
View File
@@ -38,10 +38,11 @@
import '../app.postcss'; import '../app.postcss';
export let data;
let supabase = getSupabase(); let supabase = getSupabase();
let path = $page.url?.pathname?.replace('/', ''); let path = $page.url?.pathname?.replace('/', '');
let theme = 'white'; let theme = 'white';
let skipAuth = false;
const delayedPreloading = derived(navigating, (currentPreloading, set) => { const delayedPreloading = derived(navigating, (currentPreloading, set) => {
setTimeout(() => set(currentPreloading), 250); setTimeout(() => set(currentPreloading), 250);
@@ -114,7 +115,7 @@
// Set user in sentry // Set user in sentry
Sentry.setUser($profile); Sentry.setUser($profile);
if ($appStore.isStudentDomain) { if (data.isOrgSite) {
const { data, error } = await supabase const { data, error } = await supabase
.from('organizationmember') .from('organizationmember')
.insert({ .insert({
@@ -156,7 +157,7 @@
const orgRes = await getOrganizations($profile.id); const orgRes = await getOrganizations($profile.id);
// student redirect // student redirect
if ($appStore.isStudentDomain) { if (data.isOrgSite) {
if (params.has('redirect')) { if (params.has('redirect')) {
goto(params.get('redirect') || ''); goto(params.get('redirect') || '');
} else { } else {
@@ -176,23 +177,6 @@
} }
} }
function setOrgBasedOnUrl(host) {
if (browser) {
const debug = localStorage.getItem('role') === 'student';
const matches = host.match(/([a-z0-9]+).*classroomio[.]com/);
const subdomain = matches?.[1] ?? '';
if (!blockedSubdomain.includes(subdomain)) {
const answer = Array.isArray(matches) ? !!subdomain && subdomain !== 'www' : false;
$appStore.isStudentDomain = debug || answer;
$appStore.siteNameFromDomain = debug ? 'codingdojo' : subdomain;
} else if (subdomain === 'play' || debug) {
skipAuth = true;
}
}
}
function handleResize() { function handleResize() {
isMobile.update(() => window.innerWidth <= 760); isMobile.update(() => window.innerWidth <= 760);
} }
@@ -201,7 +185,7 @@
console.log( console.log(
'Welcome to ClassroomIO, we are grateful you chose us.', 'Welcome to ClassroomIO, we are grateful you chose us.',
$page.url.host, $page.url.host,
`\nIs student domain: ${$appStore.isStudentDomain}` `\nIs student domain: ${data.isOrgSite}`
); );
// Disable GA on localhost // Disable GA on localhost
if (dev) { if (dev) {
@@ -234,7 +218,7 @@
} }
// Skip Authentication // Skip Authentication
if (skipAuth) return; if (data.skipAuth) return;
// Authentication Steps // Authentication Steps
if (event === 'SIGNED_IN') { if (event === 'SIGNED_IN') {
@@ -252,8 +236,10 @@
toggleBodyByTheme($appStore.isDark); toggleBodyByTheme($appStore.isDark);
} }
if ($appStore.isStudentDomain) { if (data.isOrgSite) {
getCurrentOrg($appStore.siteNameFromDomain); $appStore.orgSiteName = data.orgSiteName;
$appStore.isOrgSite = data.isOrgSite;
getCurrentOrg(data.orgSiteName);
} }
return () => { return () => {
@@ -264,7 +250,6 @@
$: path = $page.url?.pathname?.replace('/', ''); $: path = $page.url?.pathname?.replace('/', '');
$: theme = $appStore.isDark ? 'g100' : 'white'; $: theme = $appStore.isDark ? 'g100' : 'white';
$: setOrgBasedOnUrl($page.url.host);
</script> </script>
<svelte:window on:resize={handleResize} /> <svelte:window on:resize={handleResize} />
@@ -277,10 +262,10 @@
<Snackbar /> <Snackbar />
{#if skipAuth} {#if data.skipAuth}
<PlayQuiz /> <PlayQuiz />
{:else if $appStore.isStudentDomain && !path} {:else if data.isOrgSite && !path}
<OrgLandingPage /> <OrgLandingPage orgSiteName={data.orgSiteName} />
{:else} {:else}
<main class="dark:bg-gray-800"> <main class="dark:bg-gray-800">
{#if $navigating && $delayedPreloading} {#if $navigating && $delayedPreloading}
@@ -295,8 +280,9 @@
<LMSNavigation /> <LMSNavigation />
{:else} {:else}
<LandingNavigation <LandingNavigation
logo={$appStore.isStudentDomain ? $currentOrg.avatar_url : undefined} isOrgSite={data.isOrgSite}
orgName={$appStore.isStudentDomain ? $currentOrg.name : undefined} logo={data.isOrgSite ? $currentOrg.avatar_url : undefined}
orgName={data.isOrgSite ? $currentOrg.name : undefined}
disableLogin={false} disableLogin={false}
/> />
{/if} {/if}
Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB