CLASSROOMIO-T-42: Limit subdomain names available to organizations

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

Merge-request: CLASSROOMIO-MR-9
Merged-by: Digdippa Team <[email protected]>
This commit is contained in:
Rotimi Best
2023-05-11 15:15:18 +00:00
committed by Space Cloud
co-authored by Rotimi Best
parent 1388a41097
commit 0aa35e3dde
24 changed files with 121 additions and 43 deletions
@@ -1,5 +1,7 @@
<script> <script>
import { set, get, cloneDeep } from 'lodash'; import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import TextEditor from '../../../TextEditor/index.svelte'; import TextEditor from '../../../TextEditor/index.svelte';
export let course; export let course;
@@ -1,5 +1,7 @@
<script> <script>
import { set, get, cloneDeep } from 'lodash'; import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import TextEditor from '../../../TextEditor/index.svelte'; import TextEditor from '../../../TextEditor/index.svelte';
export let course; export let course;
@@ -1,5 +1,8 @@
<script> <script>
import { set, cloneDeep, isEmpty, get } from 'lodash'; import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import TextArea from '../../../Form/TextArea.svelte'; import TextArea from '../../../Form/TextArea.svelte';
import TextField from '../../../Form/TextField.svelte'; import TextField from '../../../Form/TextField.svelte';
import UploadImage from '../../../UploadImage/index.svelte'; import UploadImage from '../../../UploadImage/index.svelte';
@@ -1,5 +1,8 @@
<script> <script>
import { set, isEmpty, get, cloneDeep } from 'lodash'; import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import { Toggle } from 'carbon-components-svelte'; import { Toggle } from 'carbon-components-svelte';
import TextEditor from '../../../TextEditor/index.svelte'; import TextEditor from '../../../TextEditor/index.svelte';
import TextField from '../../../Form/TextField.svelte'; import TextField from '../../../Form/TextField.svelte';
@@ -1,5 +1,7 @@
<script> <script>
import { set, get, cloneDeep } from 'lodash'; import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import TextEditor from '../../../TextEditor/index.svelte'; import TextEditor from '../../../TextEditor/index.svelte';
export let course; export let course;
@@ -3,7 +3,7 @@
import { VARIANTS } from '../../PrimaryButton/constants'; import { VARIANTS } from '../../PrimaryButton/constants';
import type { Course } from '../../../utils/types'; import type { Course } from '../../../utils/types';
import getCurrencyFormatter from '../../../utils/functions/getCurrencyFormatter'; import getCurrencyFormatter from '../../../utils/functions/getCurrencyFormatter';
import { get } from 'lodash'; import get from 'lodash/get';
export let className = ''; export let className = '';
export let courseData: Course = { export let courseData: Course = {
@@ -46,9 +46,7 @@
{formatter?.format(calculatedCost) || calculatedCost} {formatter?.format(calculatedCost) || calculatedCost}
</p> </p>
{#if courseData?.metadata?.showDiscount} {#if courseData?.metadata?.showDiscount}
<p <p class="dark:text-white font-light text-sm text-gray-500">
class="dark:text-white font-light text-sm text-gray-500 dark:text-gray-200"
>
{discount}% Discount. {discount}% Discount.
<span class="line-through" <span class="line-through"
>{formatter?.format(courseData?.cost || 0) || courseData.cost}</span >{formatter?.format(courseData?.cost || 0) || courseData.cost}</span
@@ -70,9 +68,7 @@
variant={VARIANTS.OUTLINED} variant={VARIANTS.OUTLINED}
onClick={addToCart} onClick={addToCart}
/> />
<p <p class="dark:text-white font-light text-sm text-gray-500">
class="dark:text-white font-light text-sm text-gray-500 dark:text-gray-200"
>
Early bird offer. Buy ASAP Early bird offer. Buy ASAP
</p> </p>
</div> </div>
@@ -3,7 +3,7 @@
</script> </script>
<div <div
class="root w-72 border bg-white dark:bg-gray-800 mr-4 mb-4 rounded-md transition ease-in-out relative" class="root w-72 border bg-gray-100 dark:bg-gray-700 mr-4 mb-4 rounded-md transition ease-in-out relative"
> >
<SkeletonPlaceholder style="width: 100%; height: 10rem;" /> <SkeletonPlaceholder style="width: 100%; height: 10rem;" />
<div class="p-3 pb-0"> <div class="p-3 pb-0">
@@ -44,7 +44,7 @@
<a rel="prefetch" href="/courses/{id}" class=" text-black"> <a rel="prefetch" href="/courses/{id}" class=" text-black">
<div <div
class="root w-72 border bg-white dark:bg-gray-800 mr-4 mb-4 rounded-md transition ease-in-out relative" class="root w-72 border bg-gray-100 dark:bg-gray-700 mr-4 mb-4 rounded-md transition ease-in-out relative"
> >
<Dropdown <Dropdown
options={getOptions()} options={getOptions()}
+1 -1
View File
@@ -2,4 +2,4 @@
export let message; export let message;
</script> </script>
<p class="dark:text-white text-sm text-red-500">{message}</p> <p class="text-sm text-red-500">{message}</p>
+2 -2
View File
@@ -65,7 +65,7 @@
{/if} {/if}
<input <input
use:typeAction use:typeAction
class="form-input p-3 mt-1 block w-full border-none bg-gray-200 {inputClassName} {errorMessage class="form-input dark:text-black p-3 mt-1 block w-full border-none bg-gray-200 {inputClassName} {errorMessage
? 'border-red-500' ? 'border-red-500'
: ''}" : ''}"
on:keydown={onKeyDown} on:keydown={onKeyDown}
@@ -86,7 +86,7 @@
</span> </span>
{/if} {/if}
{#if errorMessage} {#if errorMessage}
<p class="dark:text-white text-sm text-red-500">{errorMessage}</p> <p class="text-sm text-red-500">{errorMessage}</p>
{:else if helperMessage} {:else if helperMessage}
<p class="dark:text-white text-sm"> <p class="dark:text-white text-sm">
{helperMessage} {helperMessage}
+3 -1
View File
@@ -1,4 +1,5 @@
<script> <script>
import { stores } from '@sapper/app';
import NotificationIcon from 'carbon-icons-svelte/lib/Notification20'; import NotificationIcon from 'carbon-icons-svelte/lib/Notification20';
import Moon from 'carbon-icons-svelte/lib/Moon16'; import Moon from 'carbon-icons-svelte/lib/Moon16';
import Sun from 'carbon-icons-svelte/lib/Sun16'; import Sun from 'carbon-icons-svelte/lib/Sun16';
@@ -8,6 +9,7 @@
import { toggleBodyByTheme } from '../../utils/functions/app'; import { toggleBodyByTheme } from '../../utils/functions/app';
export let navClass = ''; export let navClass = '';
const { page } = stores();
function toggleTheme() { function toggleTheme() {
$appStore.isDark = !$appStore.isDark; $appStore.isDark = !$appStore.isDark;
@@ -21,7 +23,7 @@
<ul class="flex w-full items-center"> <ul class="flex w-full items-center">
<div class=""> <div class="">
<a <a
href={window.location.pathname} href={$page.path}
title="Go to ClassroomIO Home" title="Go to ClassroomIO Home"
id="logo" id="logo"
class="text-lg" class="text-lg"
+17 -3
View File
@@ -7,6 +7,7 @@
import { VARIANTS } from '../../PrimaryButton/constants'; import { VARIANTS } from '../../PrimaryButton/constants';
import { currentOrg } from '../../../utils/store/org'; import { currentOrg } from '../../../utils/store/org';
import { supabase } from '../../../utils/functions/supabase'; import { supabase } from '../../../utils/functions/supabase';
import { blockedSubdomain } from '../../../utils/constants/app';
import { snackbarStore } from '../../Snackbar/store'; import { snackbarStore } from '../../Snackbar/store';
import { SNACKBAR_SEVERITY } from '../../Snackbar/constants'; import { SNACKBAR_SEVERITY } from '../../Snackbar/constants';
import SectionTitle from '../SectionTitle.svelte'; import SectionTitle from '../SectionTitle.svelte';
@@ -16,6 +17,10 @@
let isLoading = false; let isLoading = false;
async function handleChangeDomain() { async function handleChangeDomain() {
if (blockedSubdomain.includes(siteName || '')) {
errors = 'Sitename already exists.';
return;
}
isLoading = true; isLoading = true;
const { data: org, error } = await supabase const { data: org, error } = await supabase
@@ -50,12 +55,21 @@
} }
} }
function resetErrors(_siteName) {
if (errors) {
errors = '';
}
}
$: resetErrors(siteName);
$: setSiteName($currentOrg.siteName); $: setSiteName($currentOrg.siteName);
</script> </script>
<Grid class="border rounded border-gray-200 w-full mt-5"> <Grid class="border rounded border-gray-200 w-full mt-5">
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg"><strong>Add</strong></Column> <Column sm={2} md={2} lg={4} class="text-lg"
><SectionTitle>Add</SectionTitle></Column
>
<Column sm={2} md={6} lg={8}> <Column sm={2} md={6} lg={8}>
<p class="text-md text-gray-500 dark:text-gray-200 mb-5"> <p class="text-md text-gray-500 dark:text-gray-200 mb-5">
Add your team mates or collaborators to your organization. Start working Add your team mates or collaborators to your organization. Start working
@@ -85,7 +99,7 @@
</Column> </Column>
</Row> </Row>
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg" <Column sm={2} md={2} lg={4} class="text-lg"
><SectionTitle>Custom domain</SectionTitle></Column ><SectionTitle>Custom domain</SectionTitle></Column
> >
@@ -78,8 +78,8 @@
} }
</script> </script>
<Grid class="border rounded border-gray-200 w-full mt-5"> <Grid class="border-c rounded border-gray-200 w-full mt-5">
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} <Column sm={2} md={2} lg={4}
><SectionTitle>Organization Profile</SectionTitle></Column ><SectionTitle>Organization Profile</SectionTitle></Column
> >
@@ -104,13 +104,13 @@
/> />
</Column> </Column>
</Row> </Row>
<!-- <Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <!-- <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}><SectionTitle>Theme</SectionTitle></Column> <Column sm={2} md={2} lg={4}><SectionTitle>Theme</SectionTitle></Column>
<Column sm={2} md={6} lg={8}> <Column sm={2} md={6} lg={8}>
<h4 class="dark:text-white">Set your brand</h4> <h4 class="dark:text-white">Set your brand</h4>
</Column> </Column>
</Row> --> </Row> -->
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} <Column sm={2} md={2} lg={4}
><SectionTitle>Custom Domain</SectionTitle></Column ><SectionTitle>Custom Domain</SectionTitle></Column
> >
@@ -127,7 +127,7 @@
/> />
</Column> </Column>
</Row> </Row>
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}><SectionTitle>Team</SectionTitle></Column> <Column sm={2} md={2} lg={4}><SectionTitle>Team</SectionTitle></Column>
<Column sm={2} md={6} lg={8}> <Column sm={2} md={6} lg={8}>
<h4 class="dark:text-white">Set up your website</h4> <h4 class="dark:text-white">Set up your website</h4>
+3 -3
View File
@@ -81,8 +81,8 @@
} }
</script> </script>
<Grid class="border rounded border-gray-200 w-full mt-5"> <Grid class="border-c rounded border-gray-200 w-full mt-5">
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}> <Column sm={2} md={2} lg={4}>
<SectionTitle>Profile Picture</SectionTitle> <SectionTitle>Profile Picture</SectionTitle>
</Column> </Column>
@@ -94,7 +94,7 @@
/> />
</Column> </Column>
</Row> </Row>
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4}> <Column sm={2} md={2} lg={4}>
<SectionTitle>Personal Information</SectionTitle> <SectionTitle>Personal Information</SectionTitle>
</Column> </Column>
@@ -128,7 +128,7 @@
</script> </script>
<Grid class="border rounded border-gray-200 w-full mt-5"> <Grid class="border rounded border-gray-200 w-full mt-5">
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg" <Column sm={2} md={2} lg={4} class="text-lg"
><SectionTitle>Add</SectionTitle></Column ><SectionTitle>Add</SectionTitle></Column
> >
@@ -162,7 +162,7 @@
</Column> </Column>
</Row> </Row>
<Row class="py-7 border border-t-0 border-l-0 border-r-0 border-gray-300"> <Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg" <Column sm={2} md={2} lg={4} class="text-lg"
><SectionTitle>Members</SectionTitle></Column ><SectionTitle>Members</SectionTitle></Column
> >
+25 -5
View File
@@ -1,6 +1,6 @@
<script context="module"> <script context="module">
export function preload(page, { config }) { export function preload(page, { config, host }) {
return { config }; return { config, host };
} }
</script> </script>
@@ -8,8 +8,9 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
import { stores, goto } from '@sapper/app'; import { stores, goto } from '@sapper/app';
import _ from 'lodash'; import isEmpty from 'lodash/isEmpty';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { Theme } from 'carbon-components-svelte';
import { Moon } from 'svelte-loading-spinners'; import { Moon } from 'svelte-loading-spinners';
import Tailwindcss from '../components/Tailwindcss.svelte'; import Tailwindcss from '../components/Tailwindcss.svelte';
import LandingNavigation from '../components/Navigation/index.svelte'; import LandingNavigation from '../components/Navigation/index.svelte';
@@ -30,13 +31,17 @@
import { toggleBodyByTheme } from '../utils/functions/app'; import { toggleBodyByTheme } from '../utils/functions/app';
import { appStore } from '../utils/store/app'; import { appStore } from '../utils/store/app';
// import 'carbon-components-svelte/css/all.css';
export let segment; export let segment;
export let config; export let config;
export let host;
let { page, preloading } = stores(); let { page, preloading } = stores();
let supabase = getSupabase(config); let supabase = getSupabase(config);
let path = $page.path.replace('/', ''); let path = $page.path.replace('/', '');
let theme = 'white';
const delayedPreloading = derived(preloading, (currentPreloading, set) => { const delayedPreloading = derived(preloading, (currentPreloading, set) => {
setTimeout(() => set(currentPreloading), 250); setTimeout(() => set(currentPreloading), 250);
@@ -98,7 +103,7 @@
const orgRes = await getOrganizations($profile.id); const orgRes = await getOrganizations($profile.id);
// If user coming to login page, then // If user coming to login page, then
if (_.isEmpty(orgRes.orgs)) { if (isEmpty(orgRes.orgs)) {
goto(ROUTE.ONBOARDING); goto(ROUTE.ONBOARDING);
} else if ( } else if (
['login', 'signup', 'onboarding'].includes(path) || ['login', 'signup', 'onboarding'].includes(path) ||
@@ -123,7 +128,7 @@
} }
onMount(() => { onMount(() => {
console.log('Welcome to ClassroomIO, we are grateful you chose us.'); console.log('Welcome to ClassroomIO, we are grateful you chose us.', host);
// Disable GA on localhost // Disable GA on localhost
if (config && !config.isProd) { if (config && !config.isProd) {
console.log('disable GA'); console.log('disable GA');
@@ -179,14 +184,21 @@
// } // }
$: path = $page.path.replace('/', ''); $: path = $page.path.replace('/', '');
$: theme = $appStore.isDark ? 'g100' : 'white';
</script> </script>
<svelte:window on:resize={handleResize} /> <svelte:window on:resize={handleResize} />
<svelte:head> <svelte:head>
<link href="/css/carbon.css" rel="stylesheet" /> <link href="/css/carbon.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://unpkg.com/carbon-components-svelte/css/all.css"
/>
</svelte:head> </svelte:head>
<Tailwindcss /> <Tailwindcss />
<Theme bind:theme />
<!-- <Nav {segment} /> --> <!-- <Nav {segment} /> -->
<Snackbar /> <Snackbar />
@@ -262,4 +274,12 @@
:global(.dark svg.dark path) { :global(.dark svg.dark path) {
fill: #fff; fill: #fff;
} }
:global(.border-c) {
border: 1px solid var(--border-color);
}
:global(.border-bottom-c) {
border-bottom: 1px solid var(--border-color);
}
</style> </style>
+1 -1
View File
@@ -98,7 +98,7 @@
errorMessage={errors.email} errorMessage={errors.email}
/> />
{#if submitError} {#if submitError}
<p class="dark:text-white text-sm text-red-500">{submitError}</p> <p class="text-sm text-red-500">{submitError}</p>
{/if} {/if}
</div> </div>
+1 -1
View File
@@ -81,7 +81,7 @@
errorMessage={errors.password} errorMessage={errors.password}
/> />
{#if submitError} {#if submitError}
<p class="dark:text-white text-sm text-red-500">{submitError}</p> <p class="text-sm text-red-500">{submitError}</p>
{/if} {/if}
<div class="w-full text-right"> <div class="w-full text-right">
<a class="text-md text-blue-700" href="/forgot"> Forgot password? </a> <a class="text-md text-blue-700" href="/forgot"> Forgot password? </a>
+10 -2
View File
@@ -15,6 +15,7 @@
import { orgs, currentOrg } from '../utils/store/org'; import { orgs, currentOrg } from '../utils/store/org';
import { onboardingValidation } from '../utils/functions/validator'; import { onboardingValidation } from '../utils/functions/validator';
import { supabase } from '../utils/functions/supabase'; import { supabase } from '../utils/functions/supabase';
import { blockedSubdomain } from '../utils/constants/app';
interface OnboardingField { interface OnboardingField {
fullname?: string; fullname?: string;
@@ -110,6 +111,13 @@
} }
if (step === 1) { if (step === 1) {
// Validate if domain is among our seculeded subdomains
if (blockedSubdomain.includes(fields.siteName || '')) {
errors.siteName = 'Sitename already exists.';
loading = false;
return;
}
const { data: org, error } = await supabase.from('organization').insert({ const { data: org, error } = await supabase.from('organization').insert({
name: fields.orgName, name: fields.orgName,
siteName: fields.siteName, siteName: fields.siteName,
@@ -276,7 +284,7 @@
{/each} {/each}
<!-- Goal: Error message --> <!-- Goal: Error message -->
{#if errors.goal} {#if errors.goal}
<p class="dark:text-white text-sm text-red-500"> <p class="text-sm text-red-500">
{errors.goal} {errors.goal}
</p> </p>
{/if} {/if}
@@ -308,7 +316,7 @@
{/each} {/each}
<!-- Goal: Error message --> <!-- Goal: Error message -->
{#if errors.source} {#if errors.source}
<p class="dark:text-white text-sm text-red-500"> <p class="text-sm text-red-500">
{errors.source} {errors.source}
</p> </p>
{/if} {/if}
+1 -1
View File
@@ -97,7 +97,7 @@
isRequired isRequired
/> />
{#if submitError} {#if submitError}
<p class="dark:text-white text-sm text-red-500">{submitError}</p> <p class="text-sm text-red-500">{submitError}</p>
{/if} {/if}
</div> </div>
+1 -1
View File
@@ -124,7 +124,7 @@
isRequired isRequired
/> />
{#if submitError} {#if submitError}
<p class="dark:text-white text-sm text-red-500">{submitError}</p> <p class="text-sm text-red-500">{submitError}</p>
{/if} {/if}
</div> </div>
+2
View File
@@ -33,9 +33,11 @@ express() // You can also use Express
session: async (req) => { session: async (req) => {
const { user } = await supabase.auth.api.getUserByCookie(req); const { user } = await supabase.auth.api.getUserByCookie(req);
console.log('cookie calling'); console.log('cookie calling');
// console.log('req', req.headers.host);
return { return {
user: JSON.stringify(user), user: JSON.stringify(user),
config, config,
host: req.headers.host,
}; };
}, },
}) })
+24
View File
@@ -0,0 +1,24 @@
export const blockedSubdomain = [
'app',
'apps',
'support',
'help',
'teachers',
'church',
'tech',
'build',
'community',
'conference',
'campaign',
'blog',
'forum',
'mobile',
'dev',
'stage',
'prod',
'group',
'groups',
'academy',
'schools',
'training',
];
+1 -1
View File
@@ -2,6 +2,6 @@ export function toggleBodyByTheme(isDark) {
if (isDark) { if (isDark) {
document.body.className = document.body.className.concat(' ', 'dark'); document.body.className = document.body.className.concat(' ', 'dark');
} else { } else {
document.body.className = document.body.className.replace('dark', ''); document.body.className = document.body.className.replace(' dark', '');
} }
} }