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 <best@classroomio.com>
This commit is contained in:
Adeyemi Adetunji
2024-10-06 01:20:32 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 1ee02cdf3a
commit 947181a3c5
19 changed files with 292 additions and 80 deletions
+4 -1
View File
@@ -1,3 +1,6 @@
module.exports = { module.exports = {
extends: ['custom/svelte'] extends: ['custom/svelte'],
rules: {
'@typescript-eslint/ban-ts-comment': 'off'
}
}; };
-14
View File
@@ -1,14 +0,0 @@
{
"useTabs": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"overrides": [
{
"files": ["*.svelte"],
"options": {
"svelteBracketNewLine": true
}
}
]
}
+2
View File
@@ -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 />
+110 -7
View File
@@ -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
-4
View File
@@ -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');
+32
View File
@@ -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(svelte@4.2.19)
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
/color2k@2.0.3:
resolution: {integrity: sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==}
dev: false
/colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
dev: false
/combined-stream@1.0.8: /combined-stream@1.0.8:
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'}
/svelte-awesome-color-picker@3.1.4(svelte@4.2.19):
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(svelte@4.2.19)
dev: false
/svelte-awesome-slider@1.1.2(svelte@4.2.19):
resolution: {integrity: sha512-HFIWwq6rtX6aXyc6ns2R0P6pDLYIr6nRjTPyAddAiTAgZqsohAoEfOEwKKqMmZ6Q+CH48zzcFdyo6lFkBJvyQw==}
peerDependencies:
svelte: ^3.54.0 || ^4.0.0 || ^5.0.0
dependencies:
svelte: 4.2.19
dev: false
/svelte-calendar@3.1.6: /svelte-calendar@3.1.6:
resolution: {integrity: sha512-jOHiPlxBAa1LGVFQZoczdAGnCSI2RKcuQQHj32TjLbXO/P2Povx4JphPEcP7XjKj/s/jimBC6xQyFhyjD0vxbg==} resolution: {integrity: sha512-jOHiPlxBAa1LGVFQZoczdAGnCSI2RKcuQQHj32TjLbXO/P2Povx4JphPEcP7XjKj/s/jimBC6xQyFhyjD0vxbg==}
dependencies: dependencies: