feat: custom org brand color (#474)
* feat: add package * feat: add a new color picker * feat: integrate with supabase * feat: add translation * run translation script * feat: hover state * quick fix * fix: requested PR fixes * update lock * ux improvements * remove log * cleanup and improve code --------- Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
parent
1ee02cdf3a
commit
947181a3c5
@@ -1,3 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['custom/svelte']
|
extends: ['custom/svelte'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"useTabs": false,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"printWidth": 100,
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.svelte"],
|
|
||||||
"options": {
|
|
||||||
"svelteBracketNewLine": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"canvas-confetti": "^1.6.0",
|
"canvas-confetti": "^1.6.0",
|
||||||
|
"color2k": "^2.0.3",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"d3": "^7.8.5",
|
"d3": "^7.8.5",
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
"shared": "workspace:*",
|
"shared": "workspace:*",
|
||||||
"sirv": "^2.0.3",
|
"sirv": "^2.0.3",
|
||||||
"stripe": "^14.22.0",
|
"stripe": "^14.22.0",
|
||||||
|
"svelte-awesome-color-picker": "^3.1.4",
|
||||||
"svelte-email": "^0.0.4",
|
"svelte-email": "^0.0.4",
|
||||||
"svelte-meta-tags": "^3.1.2",
|
"svelte-meta-tags": "^3.1.2",
|
||||||
"svelte-ui": "workspace:*",
|
"svelte-ui": "workspace:*",
|
||||||
|
|||||||
@@ -205,9 +205,9 @@
|
|||||||
|
|
||||||
<Grid class="border rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
|
<Grid class="border rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
|
||||||
<Row class="py-7 border-bottom-c">
|
<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>{$t('components.settings.domains.add')}</SectionTitle></Column
|
<SectionTitle>{$t('components.settings.domains.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-white mb-5">
|
<p class="text-md text-gray-500 dark:text-white mb-5">
|
||||||
{$t('settings.organization.organization_profile.custom_domain.body')}
|
{$t('settings.organization.organization_profile.custom_domain.body')}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import ColorPicker from 'svelte-awesome-color-picker';
|
||||||
import { Grid, Row, Column } from 'carbon-components-svelte';
|
import { Grid, Row, Column } from 'carbon-components-svelte';
|
||||||
import FlashFilled from 'carbon-icons-svelte/lib/FlashFilled.svelte';
|
import FlashFilled from 'carbon-icons-svelte/lib/FlashFilled.svelte';
|
||||||
import TextField from '$lib/components/Form/TextField.svelte';
|
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import { isFreePlan } from '$lib/utils/store/org';
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { t } from '$lib/utils/functions/translations';
|
||||||
import UploadImage from '$lib/components/UploadImage/index.svelte';
|
|
||||||
import { supabase } from '$lib/utils/functions/supabase';
|
import { supabase } from '$lib/utils/functions/supabase';
|
||||||
import { snackbar } from '$lib/components/Snackbar/store';
|
import { snackbar } from '$lib/components/Snackbar/store';
|
||||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||||
import SectionTitle from '../SectionTitle.svelte';
|
|
||||||
import { t } from '$lib/utils/functions/translations';
|
|
||||||
import { isFreePlan } from '$lib/utils/store/org';
|
|
||||||
import { updateOrgNameValidation } from '$lib/utils/functions/validator';
|
import { updateOrgNameValidation } from '$lib/utils/functions/validator';
|
||||||
import { setTheme } from '$lib/utils/functions/theme';
|
import { setTheme, setCustomTheme, injectCustomTheme } from '$lib/utils/functions/theme';
|
||||||
|
|
||||||
|
import SectionTitle from '../SectionTitle.svelte';
|
||||||
|
import TextField from '$lib/components/Form/TextField.svelte';
|
||||||
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
|
import UploadImage from '$lib/components/UploadImage/index.svelte';
|
||||||
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
|
|
||||||
let avatar;
|
let avatar;
|
||||||
|
|
||||||
@@ -24,8 +27,9 @@
|
|||||||
let errors: Error = {
|
let errors: Error = {
|
||||||
orgName: ''
|
orgName: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
let hex = '';
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
rose: 'theme-rose',
|
rose: 'theme-rose',
|
||||||
green: 'theme-green',
|
green: 'theme-green',
|
||||||
@@ -43,6 +47,8 @@
|
|||||||
.concat(!!t ? ' ' : '', t);
|
.concat(!!t ? ' ' : '', t);
|
||||||
$currentOrg.theme = t;
|
$currentOrg.theme = t;
|
||||||
|
|
||||||
|
hex = '';
|
||||||
|
|
||||||
setTheme(t);
|
setTheme(t);
|
||||||
|
|
||||||
const res = await supabase
|
const res = await supabase
|
||||||
@@ -54,6 +60,24 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleCustomTheme() {
|
||||||
|
if (!hex) return;
|
||||||
|
|
||||||
|
injectCustomTheme(hex);
|
||||||
|
setCustomTheme('theme-custom');
|
||||||
|
|
||||||
|
$currentOrg.theme = hex;
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('organization')
|
||||||
|
.update({ theme: hex })
|
||||||
|
.match({ id: $currentOrg.id });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
snackbar.error('Failed to update theme: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleUpdate() {
|
async function handleUpdate() {
|
||||||
errors = updateOrgNameValidation($currentOrg.name) as Error;
|
errors = updateOrgNameValidation($currentOrg.name) as Error;
|
||||||
|
|
||||||
@@ -65,7 +89,7 @@
|
|||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
const updates = {
|
const updates: Record<string, string> = {
|
||||||
name: $currentOrg.name
|
name: $currentOrg.name
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,7 +124,7 @@
|
|||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = error.message;
|
let message = error as string;
|
||||||
if (message.includes('profile_username_key')) {
|
if (message.includes('profile_username_key')) {
|
||||||
message = $t('snackbar.lms.error.username_exists');
|
message = $t('snackbar.lms.error.username_exists');
|
||||||
}
|
}
|
||||||
@@ -112,17 +136,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotoSetting(pathname) {
|
function gotoSettings(pathname) {
|
||||||
goto(`${$currentOrgPath}/settings${pathname}`);
|
goto(`${$currentOrgPath}/settings${pathname}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setHex(theme: string) {
|
||||||
|
if (hex || theme.includes('theme-')) return;
|
||||||
|
hex = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: setHex($currentOrg.theme);
|
||||||
|
$: isCustomTheme = hex && !hex.includes('theme-');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
|
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
|
||||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||||
<Column sm={4} md={4} lg={4}
|
<Column sm={4} md={4} lg={4}>
|
||||||
><SectionTitle>{$t('settings.organization.organization_profile.heading')}</SectionTitle
|
<SectionTitle>
|
||||||
></Column
|
{$t('settings.organization.organization_profile.heading')}
|
||||||
>
|
</SectionTitle>
|
||||||
|
</Column>
|
||||||
<Column sm={8} md={8} lg={8} class="mt-2 lg:mt-0 flex flex-col items-center lg:items-start">
|
<Column sm={8} md={8} lg={8} class="mt-2 lg:mt-0 flex flex-col items-center lg:items-start">
|
||||||
<TextField
|
<TextField
|
||||||
label={$t('settings.organization.organization_profile.organization_name')}
|
label={$t('settings.organization.organization_profile.organization_name')}
|
||||||
@@ -145,7 +178,7 @@
|
|||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c relative">
|
||||||
<Column sm={4} md={4} lg={4}
|
<Column sm={4} md={4} lg={4}
|
||||||
><SectionTitle>{$t('settings.organization.organization_profile.theme.heading')}</SectionTitle
|
><SectionTitle>{$t('settings.organization.organization_profile.theme.heading')}</SectionTitle
|
||||||
></Column
|
></Column
|
||||||
@@ -155,55 +188,84 @@
|
|||||||
{$t('settings.organization.organization_profile.theme.sub_heading')}
|
{$t('settings.organization.organization_profile.theme.sub_heading')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex items-center gap-5">
|
||||||
<button
|
<button
|
||||||
class="rounded-full border-2 {$currentOrg.theme === themes.default &&
|
class="rounded-full border-2 {$currentOrg.theme === themes.default &&
|
||||||
'border-[#1d4ee2]'} mr-3 flex items-center justify-center"
|
'border-[#1d4ee2]'} flex items-center justify-center h-fit"
|
||||||
on:click={handleChangeTheme(themes.default)}
|
on:click={handleChangeTheme(themes.default)}
|
||||||
>
|
>
|
||||||
<div class="w-3 h-3 md:w-6 md:h-6 bg-[#1d4ee2] rounded-full m-1" />
|
<div class="w-6 h-6 md:w-6 md:h-6 bg-[#1d4ee2] rounded-full m-1" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="rounded-full border-2 {$currentOrg.theme === themes.rose &&
|
class="rounded-full border-2 {$currentOrg.theme === themes.rose &&
|
||||||
'border-[#be1241]'} mr-3 flex items-center justify-center"
|
'border-[#be1241]'} flex items-center justify-center h-fit"
|
||||||
on:click={handleChangeTheme(themes.rose)}
|
on:click={handleChangeTheme(themes.rose)}
|
||||||
>
|
>
|
||||||
<div class="w-3 h-3 md:w-6 md:h-6 bg-[#be1241] rounded-full m-1" />
|
<div class="w-6 h-6 md:w-6 md:h-6 bg-[#be1241] rounded-full m-1" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="rounded-full border-2 {$currentOrg.theme === themes.green &&
|
class="rounded-full border-2 {$currentOrg.theme === themes.green &&
|
||||||
'border-[#0c891b]'} mr-3 flex items-center justify-center"
|
'border-[#0c891b]'} flex items-center justify-center h-fit"
|
||||||
on:click={handleChangeTheme(themes.green)}
|
on:click={handleChangeTheme(themes.green)}
|
||||||
>
|
>
|
||||||
<div class="w-3 h-3 md:w-6 md:h-6 bg-[#0c891b] rounded-full m-1" />
|
<div class="w-6 h-6 md:w-6 md:h-6 bg-[#0c891b] rounded-full m-1" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="rounded-full border-2 {$currentOrg.theme === themes.orange &&
|
class="rounded-full border-2 {$currentOrg.theme === themes.orange &&
|
||||||
'border-[#cc4902]'} mr-3 flex items-center justify-center"
|
'border-[#cc4902]'} flex items-center justify-center h-fit"
|
||||||
on:click={handleChangeTheme(themes.orange)}
|
on:click={handleChangeTheme(themes.orange)}
|
||||||
>
|
>
|
||||||
<div class="w-3 h-3 md:w-6 md:h-6 bg-[#cc4902] rounded-full m-1" />
|
<div class="w-6 h-6 md:w-6 md:h-6 bg-[#cc4902] rounded-full m-1" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="rounded-full border-2 {$currentOrg.theme === themes.violet &&
|
class="rounded-full border-2 {$currentOrg.theme === themes.violet &&
|
||||||
'border-[#cf00ce]'} mr-3 flex items-center justify-center"
|
'border-[#cf00ce]'} flex items-center justify-center h-fit"
|
||||||
on:click={handleChangeTheme(themes.violet)}
|
on:click={handleChangeTheme(themes.violet)}
|
||||||
>
|
>
|
||||||
<div class="w-3 h-3 md:w-6 md:h-6 bg-[#cf00ce] rounded-full m-1" />
|
<div class="w-6 h-6 md:w-6 md:h-6 bg-[#cf00ce] rounded-full m-1" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-fit h-auto border-2 {isCustomTheme &&
|
||||||
|
'border-primary-700'} rounded-full relative group"
|
||||||
|
>
|
||||||
|
<!-- plus icon positioned over the color picker -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 flex items-center justify-center pointer-events-none transition-opacity duration-200"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-6 h-6 text-{isCustomTheme ? 'white' : 'black'} z-10 opacity-100"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<ColorPicker
|
||||||
|
position="responsive"
|
||||||
|
label=""
|
||||||
|
bind:hex
|
||||||
|
on:input={handleCustomTheme}
|
||||||
|
nullable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||||
<Column sm={4} md={4} lg={4}
|
<Column sm={4} md={4} lg={4}>
|
||||||
><SectionTitle
|
<SectionTitle>
|
||||||
>{$t('settings.organization.organization_profile.customize_lms.heading')}</SectionTitle
|
{$t('settings.organization.organization_profile.customize_lms.heading')}
|
||||||
></Column
|
</SectionTitle>
|
||||||
>
|
</Column>
|
||||||
<Column sm={8} md={8} lg={8}>
|
<Column sm={8} md={8} lg={8}>
|
||||||
<h4 class="dark:text-white lg:mt-0">
|
<h4 class="dark:text-white lg:mt-0">
|
||||||
{$t('settings.organization.organization_profile.customize_lms.sub_heading')}
|
{$t('settings.organization.organization_profile.customize_lms.sub_heading')}
|
||||||
@@ -214,7 +276,7 @@
|
|||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
||||||
variant={VARIANTS.OUTLINED}
|
variant={VARIANTS.OUTLINED}
|
||||||
onClick={() => gotoSetting('/customize-lms')}
|
onClick={() => gotoSettings('/customize-lms')}
|
||||||
>
|
>
|
||||||
{$t('settings.organization.organization_profile.customize_lms.button')}
|
{$t('settings.organization.organization_profile.customize_lms.button')}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
@@ -236,7 +298,7 @@
|
|||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
||||||
variant={VARIANTS.OUTLINED}
|
variant={VARIANTS.OUTLINED}
|
||||||
onClick={() => gotoSetting('/domains')}
|
onClick={() => gotoSettings('/domains')}
|
||||||
>
|
>
|
||||||
{#if $isFreePlan}
|
{#if $isFreePlan}
|
||||||
<FlashFilled size={16} class="text-blue-700" />
|
<FlashFilled size={16} class="text-blue-700" />
|
||||||
@@ -260,7 +322,7 @@
|
|||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
||||||
variant={VARIANTS.OUTLINED}
|
variant={VARIANTS.OUTLINED}
|
||||||
onClick={() => gotoSetting('/teams')}
|
onClick={() => gotoSettings('/teams')}
|
||||||
>
|
>
|
||||||
{#if $isFreePlan}
|
{#if $isFreePlan}
|
||||||
<FlashFilled size={16} class="text-blue-700" />
|
<FlashFilled size={16} class="text-blue-700" />
|
||||||
|
|||||||
@@ -33,8 +33,6 @@
|
|||||||
errorMessage = null; // Clear error message on successful load
|
errorMessage = null; // Clear error message on successful load
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
$: isDisabled = isUploading;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="width-fit p-3 flex {flexDirection} items-center justify-between gap-5">
|
<section class="width-fit p-3 flex {flexDirection} items-center justify-between gap-5">
|
||||||
@@ -57,15 +55,16 @@
|
|||||||
|
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<button
|
<button
|
||||||
class="width-fit text-primary-700 flex flex-col items-center text-sm {isDisabled
|
class="width-fit text-primary-700 flex flex-col items-center text-sm {isDisabled ||
|
||||||
|
isUploading
|
||||||
? 'opacity-50 cursor-not-allowed'
|
? 'opacity-50 cursor-not-allowed'
|
||||||
: 'cursor-pointer'}"
|
: 'cursor-pointer'}"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (!isDisabled) {
|
if (!isDisabled || isUploading) {
|
||||||
fileinput.click();
|
fileinput.click();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled || isUploading}
|
||||||
>
|
>
|
||||||
{#if isUploading}
|
{#if isUploading}
|
||||||
<Loading withOverlay={false} small />
|
<Loading withOverlay={false} small />
|
||||||
|
|||||||
@@ -1,14 +1,117 @@
|
|||||||
export function setTheme(theme?: string) {
|
import { tc } from '$lib/utils/functions/trycatch';
|
||||||
// In case the default theme is added but another theme exists
|
import { darken, lighten } from 'color2k';
|
||||||
if (!theme && document.body.className.includes('theme-')) {
|
|
||||||
|
function updateThemeClassInBody(newClass: string, customRegex?: string) {
|
||||||
const regex = /theme-[\w]+/gi;
|
const regex = /theme-[\w]+/gi;
|
||||||
document.body.className = document.body.className.replace(regex, theme || '');
|
document.body.className = document.body.className.replace(customRegex ?? regex, newClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTheme(theme: string) {
|
||||||
|
const _theme = theme || '';
|
||||||
|
|
||||||
|
// this condition checks if it's a hex code from the db or a specified theme
|
||||||
|
if (theme && !theme.includes('theme-')) {
|
||||||
|
// add the "custom-theme" styles to the head tag
|
||||||
|
injectCustomTheme(theme);
|
||||||
|
|
||||||
|
// add the "custom-theme" classname to the head tag
|
||||||
|
setCustomTheme('theme-custom');
|
||||||
|
} else if (!theme && document.body.className.includes('theme-')) {
|
||||||
|
// if no theme and a theme is already applied, remove it
|
||||||
|
updateThemeClassInBody(_theme);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case theme already exists in dom, don't add
|
// In case theme already exists in dom, don't add
|
||||||
if (document.body.className.includes(theme || '')) return;
|
if (document.body.className.includes(_theme)) return;
|
||||||
|
|
||||||
localStorage.setItem('theme', theme || '');
|
// set the new theme
|
||||||
document.body.className = document.body.className.concat(' ', theme || '');
|
localStorage.setItem('theme', _theme);
|
||||||
|
document.body.className = document.body.className.concat(' ', _theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCustomTheme(theme?: string) {
|
||||||
|
// In case the default theme is added but another theme exists
|
||||||
|
if (!theme && document.body.className.includes('theme-')) {
|
||||||
|
updateThemeClassInBody(theme || '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any class starting with "theme-"
|
||||||
|
updateThemeClassInBody('');
|
||||||
|
|
||||||
|
const _theme = theme || '';
|
||||||
|
// Add the new theme if it doesn't already exist
|
||||||
|
if (!document.body.className.includes(_theme)) {
|
||||||
|
localStorage.setItem('theme', _theme);
|
||||||
|
document.body.className = document.body.className.concat(' ', _theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle this functions in a try catch if hex is not valid.
|
||||||
|
const _lighten = (hex: string, no: number) => tc(() => lighten(hex, no), hex);
|
||||||
|
const _darken = (hex: string, no: number) => tc(() => darken(hex, no), hex);
|
||||||
|
|
||||||
|
export function injectCustomTheme(hex: string) {
|
||||||
|
const styleId = 'theme-custom';
|
||||||
|
let styleElement = document.getElementById(styleId);
|
||||||
|
|
||||||
|
// generate shades using color2k's lighten function
|
||||||
|
const shades = {
|
||||||
|
50: _lighten(hex, 0.7),
|
||||||
|
100: _lighten(hex, 0.6),
|
||||||
|
200: _lighten(hex, 0.5),
|
||||||
|
300: _lighten(hex, 0.4),
|
||||||
|
400: _lighten(hex, 0.3),
|
||||||
|
500: _lighten(hex, 0.2),
|
||||||
|
600: _lighten(hex, 0.1),
|
||||||
|
700: hex,
|
||||||
|
800: _darken(hex, 0.1),
|
||||||
|
900: _darken(hex, 0.2)
|
||||||
|
};
|
||||||
|
|
||||||
|
const styleContent = `
|
||||||
|
.theme-custom .bg-primary-50 { background-color: ${shades[50]} !important; }
|
||||||
|
.theme-custom .bg-primary-100 { background-color: ${shades[100]} !important; }
|
||||||
|
.theme-custom .bg-primary-200 { background-color: ${shades[200]} !important; }
|
||||||
|
.theme-custom .bg-primary-300 { background-color: ${shades[300]} !important; }
|
||||||
|
.theme-custom .bg-primary-400 { background-color: ${shades[400]} !important; }
|
||||||
|
.theme-custom .bg-primary-500 { background-color: ${shades[500]} !important; }
|
||||||
|
.theme-custom .bg-primary-600 { background-color: ${shades[600]} !important; }
|
||||||
|
.theme-custom .bg-primary-700 { background-color: ${shades[700]} !important; }
|
||||||
|
.theme-custom .bg-primary-800 { background-color: ${shades[800]} !important; }
|
||||||
|
.theme-custom .bg-primary-900 { background-color: ${shades[900]} !important; }
|
||||||
|
|
||||||
|
.theme-custom .text-primary-50 { color: ${shades[50]} !important; }
|
||||||
|
.theme-custom .text-primary-100 { color: ${shades[100]} !important; }
|
||||||
|
.theme-custom .text-primary-200 { color: ${shades[200]} !important; }
|
||||||
|
.theme-custom .text-primary-300 { color: ${shades[300]} !important; }
|
||||||
|
.theme-custom .text-primary-400 { color: ${shades[400]} !important; }
|
||||||
|
.theme-custom .text-primary-500 { color: ${shades[500]} !important; }
|
||||||
|
.theme-custom .text-primary-600 { color: ${shades[600]} !important; }
|
||||||
|
.theme-custom .text-primary-700 { color: ${shades[700]} !important; }
|
||||||
|
.theme-custom .text-primary-800 { color: ${shades[800]} !important; }
|
||||||
|
.theme-custom .text-primary-900 { color: ${shades[900]} !important; }
|
||||||
|
|
||||||
|
.theme-custom .border-primary-50 { border-color: ${shades[50]} !important; }
|
||||||
|
.theme-custom .border-primary-100 { border-color: ${shades[100]} !important; }
|
||||||
|
.theme-custom .border-primary-200 { border-color: ${shades[200]} !important; }
|
||||||
|
.theme-custom .border-primary-300 { border-color: ${shades[300]} !important; }
|
||||||
|
.theme-custom .border-primary-400 { border-color: ${shades[400]} !important; }
|
||||||
|
.theme-custom .border-primary-500 { border-color: ${shades[500]} !important; }
|
||||||
|
.theme-custom .border-primary-600 { border-color: ${shades[600]} !important; }
|
||||||
|
.theme-custom .border-primary-700 { border-color: ${shades[700]} !important; }
|
||||||
|
.theme-custom .border-primary-800 { border-color: ${shades[800]} !important; }
|
||||||
|
.theme-custom .border-primary-900 { border-color: ${shades[900]} !important; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (styleElement) {
|
||||||
|
styleElement.innerHTML = styleContent;
|
||||||
|
} else {
|
||||||
|
styleElement = document.createElement('style');
|
||||||
|
styleElement.id = styleId;
|
||||||
|
styleElement.innerHTML = styleContent;
|
||||||
|
document.head.appendChild(styleElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export function tc<T>(tryFunc: () => T, catchFunc: T | ((e?: Error) => T)) {
|
||||||
|
let val: T;
|
||||||
|
try {
|
||||||
|
val = tryFunc();
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (typeof catchFunc === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
val = catchFunc(e);
|
||||||
|
} else {
|
||||||
|
val = catchFunc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
@@ -1245,7 +1245,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "Thema",
|
"heading": "Thema",
|
||||||
"sub_heading": "Legen Sie Ihre Markenfarbe fest"
|
"sub_heading": "Legen Sie Ihre Markenfarbe fest",
|
||||||
|
"custom_theme": "Benutzerdefiniertes Thema",
|
||||||
|
"add_theme": "Thema hinzufügen"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "LMS anpassen",
|
"heading": "LMS anpassen",
|
||||||
|
|||||||
@@ -99,7 +99,9 @@
|
|||||||
"update_organization": "Update Organization",
|
"update_organization": "Update Organization",
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "Theme",
|
"heading": "Theme",
|
||||||
"sub_heading": "Set your brand color"
|
"sub_heading": "Set your brand color",
|
||||||
|
"custom_theme": "Custom Theme",
|
||||||
|
"add_theme": "Add Theme"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "Customize LMS",
|
"heading": "Customize LMS",
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "Tema",
|
"heading": "Tema",
|
||||||
"sub_heading": "Establece el color de tu marca"
|
"sub_heading": "Establece el color de tu marca",
|
||||||
|
"custom_theme": "Tema personalizado",
|
||||||
|
"add_theme": "Agregar tema"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "Personalizar LMS",
|
"heading": "Personalizar LMS",
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "Thème",
|
"heading": "Thème",
|
||||||
"sub_heading": "Définissez la couleur de votre marque"
|
"sub_heading": "Définissez la couleur de votre marque",
|
||||||
|
"custom_theme": "Thème personnalisé",
|
||||||
|
"add_theme": "Ajouter un thème"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "Personnaliser le LMS",
|
"heading": "Personnaliser le LMS",
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "विषय",
|
"heading": "विषय",
|
||||||
"sub_heading": "अपना ब्रांड रंग सेट करें"
|
"sub_heading": "अपना ब्रांड रंग सेट करें",
|
||||||
|
"custom_theme": "कस्टम थीम",
|
||||||
|
"add_theme": "थीम जोड़ें"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "एलएमएस को अनुकूलित करें",
|
"heading": "एलएमएस को अनुकूलित करें",
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "Tema",
|
"heading": "Tema",
|
||||||
"sub_heading": "Defina a cor da sua marca"
|
"sub_heading": "Defina a cor da sua marca",
|
||||||
|
"custom_theme": "Tema personalizado",
|
||||||
|
"add_theme": "Adicionar tema"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "Personalizar LMS",
|
"heading": "Personalizar LMS",
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "Тема",
|
"heading": "Тема",
|
||||||
"sub_heading": "Установите цвет вашего бренда"
|
"sub_heading": "Установите цвет вашего бренда",
|
||||||
|
"custom_theme": "Пользовательская тема",
|
||||||
|
"add_theme": "Добавить тему"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "Настроить систему управления обучением",
|
"heading": "Настроить систему управления обучением",
|
||||||
|
|||||||
@@ -1245,7 +1245,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"heading": "chủ đề",
|
"heading": "chủ đề",
|
||||||
"sub_heading": "Đặt màu thương hiệu của bạn"
|
"sub_heading": "Đặt màu thương hiệu của bạn",
|
||||||
|
"custom_theme": "Chủ đề tùy chỉnh",
|
||||||
|
"add_theme": "Thêm chủ đề"
|
||||||
},
|
},
|
||||||
"customize_lms": {
|
"customize_lms": {
|
||||||
"heading": "Tùy chỉnh LMS",
|
"heading": "Tùy chỉnh LMS",
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export const load = async ({ url, cookies }): Promise<LoadOutput> => {
|
|||||||
const subdomain = getSubdomain(url) || '';
|
const subdomain = getSubdomain(url) || '';
|
||||||
|
|
||||||
const isDev = dev || isLocalHost;
|
const isDev = dev || isLocalHost;
|
||||||
console.log('url.host', url.host);
|
|
||||||
|
|
||||||
if (!url.host.includes('.classroomio.com') && !isLocalHost) {
|
if (!url.host.includes('.classroomio.com') && !isLocalHost) {
|
||||||
// Custom domain
|
// Custom domain
|
||||||
|
|||||||
@@ -248,10 +248,6 @@
|
|||||||
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
|
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
|
||||||
// Log key events
|
// Log key events
|
||||||
console.log(`event`, event);
|
console.log(`event`, event);
|
||||||
console.log(`session`, session);
|
|
||||||
if (event == 'PASSWORD_RECOVERY') {
|
|
||||||
console.log('PASSWORD RESET');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.includes('reset')) {
|
if (path.includes('reset')) {
|
||||||
console.log('Dont change auth when on reset page');
|
console.log('Dont change auth when on reset page');
|
||||||
|
|||||||
Generated
+32
@@ -220,6 +220,9 @@ importers:
|
|||||||
canvas-confetti:
|
canvas-confetti:
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.9.3
|
version: 1.9.3
|
||||||
|
color2k:
|
||||||
|
specifier: ^2.0.3
|
||||||
|
version: 2.0.3
|
||||||
cookie-parser:
|
cookie-parser:
|
||||||
specifier: ^1.4.6
|
specifier: ^1.4.6
|
||||||
version: 1.4.6
|
version: 1.4.6
|
||||||
@@ -280,6 +283,9 @@ importers:
|
|||||||
stripe:
|
stripe:
|
||||||
specifier: ^14.22.0
|
specifier: ^14.22.0
|
||||||
version: 14.25.0
|
version: 14.25.0
|
||||||
|
svelte-awesome-color-picker:
|
||||||
|
specifier: ^3.1.4
|
||||||
|
version: 3.1.4([email protected])
|
||||||
svelte-email:
|
svelte-email:
|
||||||
specifier: ^0.0.4
|
specifier: ^0.0.4
|
||||||
version: 0.0.4
|
version: 0.0.4
|
||||||
@@ -6307,6 +6313,14 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/[email protected]:
|
||||||
|
resolution: {integrity: sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/[email protected]:
|
||||||
|
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/[email protected]:
|
/[email protected]:
|
||||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -13635,6 +13649,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
/[email protected]([email protected]):
|
||||||
|
resolution: {integrity: sha512-tiFakxvSpCwodOSFW6CflnLVSldSlDY77rDipHfW7hvB+4gTP3y6uO+Dm9O9e7P5N7K+CNyNjjdjbZOHYkMyNQ==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3.55.1 || ^4.0.0 || ^5.0.0
|
||||||
|
dependencies:
|
||||||
|
colord: 2.9.3
|
||||||
|
svelte: 4.2.19
|
||||||
|
svelte-awesome-slider: 1.1.2([email protected])
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/[email protected]([email protected]):
|
||||||
|
resolution: {integrity: sha512-HFIWwq6rtX6aXyc6ns2R0P6pDLYIr6nRjTPyAddAiTAgZqsohAoEfOEwKKqMmZ6Q+CH48zzcFdyo6lFkBJvyQw==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3.54.0 || ^4.0.0 || ^5.0.0
|
||||||
|
dependencies:
|
||||||
|
svelte: 4.2.19
|
||||||
|
dev: false
|
||||||
|
|
||||||
/[email protected]:
|
/[email protected]:
|
||||||
resolution: {integrity: sha512-jOHiPlxBAa1LGVFQZoczdAGnCSI2RKcuQQHj32TjLbXO/P2Povx4JphPEcP7XjKj/s/jimBC6xQyFhyjD0vxbg==}
|
resolution: {integrity: sha512-jOHiPlxBAa1LGVFQZoczdAGnCSI2RKcuQQHj32TjLbXO/P2Povx4JphPEcP7XjKj/s/jimBC6xQyFhyjD0vxbg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user