feat: migrate dashboard to svelte 5 (#577)

* chore: migration to SvelteKit 2

* stage 1: migrate to svelte 5 and fix rerendering issues

* stage 2: fix more $: reactivity across dashboard

* fix merge conflicts

* chore: migrate code in main to svelte 5

* chore: update preventDefault

* chore: simplify tsconfig and remove unused mobileMenuRef in MobileMenu.svelte

* chore: enforce .ts across dashboard

* rename from classroomio-com to website

* stage-3: Replace carbon icon svelte with lucide icon

* update lock file

* chore: finish icon replacement

* fix: settings and setup page multiple rerender

* stage 3: More icon and rerendering fixes

* feat: checkpoint

* feat: update editor

* [not working] connect everything with ui build

* storybook + ui + dashboard all working independently

* add bg-black to +page.svelte

* feat: add editor to storybook

* chore: animate lucid icons (#590)

* v1 animations

* feat: change some course navigation icons & animate org navigation icons

* chore: change settings icon

* chore: use css instead of js for hover

* rename animations

---------

Co-authored-by: tunny17 <tvnji01@gmail.com>

* chore: update path to node_modules types in api tsconfig

* fix: update import path for DropdownMenu component

* fix: add rollupOptions to build configuration in vite.config.ts

* fix: remove rollupOptions from build configuration and add @floating-ui/dom dependency

* install

---------

Co-authored-by: tunny17 <tvnji01@gmail.com>
This commit is contained in:
Rotimi Best
2025-11-01 16:18:29 +01:00
committed by GitHub
co-authored by tunny17
parent 0fb66a3782
commit f5a4156c8b
159 changed files with 15225 additions and 3722 deletions
+1 -1
View File
@@ -58,7 +58,7 @@
},
"devDependencies": {
"@types/katex": "^0.16.7",
"@types/node": "^20.11.19",
"@types/node": "^20.19.0",
"@types/nodemailer": "^6.4.17",
"@typescript-eslint/eslint-plugin": "7.0.1",
"@typescript-eslint/parser": "7.0.1",
+1 -1
View File
@@ -15,7 +15,7 @@
"skipLibCheck": true,
"sourceMap": true,
"target": "ESNext",
"typeRoots": ["./node_modules/@types", "./src/types"],
"typeRoots": ["../../node_modules/@types", "./node_modules/@types", "./src/types"],
"types": ["node"]
},
"include": ["src/**/*"],
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
};
+5 -2
View File
@@ -21,6 +21,7 @@
"dependencies": {
"@carbon/charts-svelte": "^1.13.6",
"@cio/api": "workspace:*",
"@cio/ui": "workspace:*",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
"@lucide/svelte": "^0.545.0",
"@polar-sh/sveltekit": "^0.2.1",
@@ -34,6 +35,7 @@
"@types/pluralize": "0.0.30",
"ai": "^2.1.31",
"axios": "^1.8.4",
"bits-ui": "^2.14.1",
"body-parser": "^1.20.2",
"bufferutil": "^4.0.9",
"canvas-confetti": "^1.6.0",
@@ -78,8 +80,10 @@
"@babel/runtime": "^7.22.6",
"@eslint/compat": "^1.4.0",
"@eslint/js": "^9.36.0",
"@rollup/plugin-replace": "^6.0.2",
"@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tailwindcss/postcss": "^4.1.14",
"@tailwindcss/typography": "^0.5.9",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/svelte": "^4.0.5",
@@ -87,7 +91,6 @@
"@types/lodash": "^4.14.196",
"@types/node": "^20",
"all-object-keys": "^2.2.0",
"autoprefixer": "^10.4.14",
"babel-jest": "^29.7.0",
"carbon-components-svelte": "^0.79.0",
"diff": "^5.2.0",
@@ -107,7 +110,7 @@
"svelte-jester": "^3.0.0",
"svelte-loading-spinners": "^0.3.4",
"svelte-preprocess": "^6.0.0",
"tailwindcss": "^3.3.2",
"tailwindcss": "^4.1.14",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"@cio/tsconfig": "workspace:*",
+1 -2
View File
@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
'@tailwindcss/postcss': {},
}
};
@@ -1,8 +1,9 @@
/*Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
@import '@cio/ui/output.css';
@custom-variant dark (&:where(.dark, .dark *));
/** Global scrollbar styles */
html {
@@ -84,7 +85,7 @@ h6 {
margin-bottom: 10px !important;
}
.dark .prose h1,
/* .dark .prose h1,
.dark .prose h2,
.dark .prose h3,
.dark .prose h4,
@@ -95,7 +96,7 @@ h6 {
.dark .prose li::marker,
.dark .prose p {
color: #fff !important;
}
} */
iframe.iframe {
width: 100%;
@@ -192,10 +193,6 @@ svg.lucide-icon:not(.custom) {
}
}
@tailwind components;
@tailwind utilities;
/**
Rose Theme
*/
@@ -710,3 +707,312 @@ svg.lucide-icon:not(.custom) {
.theme-violet .border-primary-900 {
border-color: #890684 !important;
}
/* Tailwind v4 Custom Animations and Keyframes */
@theme {
--animate-shake-announcement: shakeAnnouncement 0.5s ease-in-out;
--animate-flip-book: flipBook 0.6s ease-in-out;
--animate-scroll-unroll: scrollUnroll 0.8s ease-out;
--animate-check-items: attendanceCount 0.8s ease-in-out;
--animate-task-complete: taskComplete 0.6s ease-in-out;
--animate-grade-glow: gradeGlow 1s ease-in-out;
--animate-people-wave: peopleWave 0.8s ease-in-out;
--animate-chart-grow: dataScan 1s ease-in-out;
--animate-screen-glow: screenGlow 1s ease-in-out;
--animate-library-expand: libraryExpand 0.7s ease-out;
--animate-template-morph: templateMorph 0.6s ease-in-out;
--animate-ripple: communityRipple 0.9s ease-out;
--animate-roll: quizRoll 0.8s ease-in-out;
--animate-gather: audienceGather 0.7s ease-in-out;
--animate-setup-configure: setupConfigure 1s ease-in-out;
}
@keyframes shakeAnnouncement {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-2px);
}
20%,
40%,
60%,
80% {
transform: translateX(2px);
}
}
@keyframes flipBook {
0% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(90deg);
}
100% {
transform: rotateY(0deg);
}
}
@keyframes scrollUnroll {
0% {
transform: scaleY(0) translateY(-50%);
opacity: 0.5;
}
50% {
transform: scaleY(0.8) translateY(-25%);
opacity: 0.8;
}
100% {
transform: scaleY(1) translateY(0);
opacity: 1;
}
}
@keyframes attendanceCount {
0% {
transform: scale(1);
opacity: 0.8;
}
20% {
transform: scale(1.1);
opacity: 1;
}
40% {
transform: scale(1.05);
opacity: 0.9;
}
60% {
transform: scale(1.15);
opacity: 1;
}
80% {
transform: scale(1.02);
opacity: 0.95;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes taskComplete {
0% {
transform: scale(1) rotate(0deg);
}
25% {
transform: scale(1.1) rotate(-5deg);
}
50% {
transform: scale(1.2) rotate(5deg);
}
75% {
transform: scale(1.1) rotate(-2deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
@keyframes gradeGlow {
0%,
100% {
transform: scale(1);
filter: brightness(1) drop-shadow(0 0 0 rgba(34, 197, 94, 0));
}
50% {
transform: scale(1.15);
filter: brightness(1.3) drop-shadow(0 0 8px rgba(34, 197, 94, 0.6));
}
}
@keyframes peopleWave {
0%,
100% {
transform: rotate(0deg) scale(1);
}
25% {
transform: rotate(-5deg) scale(1.05);
}
50% {
transform: rotate(5deg) scale(1.1);
}
75% {
transform: rotate(-3deg) scale(1.05);
}
}
@keyframes dataScan {
0% {
transform: scale(1);
filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0));
}
25% {
transform: scale(1.05);
filter: brightness(1.1) drop-shadow(0 0 4px rgba(99, 102, 241, 0.4));
}
50% {
transform: scale(1.1);
filter: brightness(1.2) drop-shadow(0 0 8px rgba(99, 102, 241, 0.6));
}
75% {
transform: scale(1.15) rotate(2deg);
filter: brightness(1.3) drop-shadow(0 0 12px rgba(99, 102, 241, 0.8));
}
100% {
transform: scale(1);
filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0));
}
}
@keyframes screenGlow {
0%,
100% {
transform: scale(1);
filter: brightness(1) drop-shadow(0 0 0 rgba(59, 130, 246, 0));
}
50% {
transform: scale(1.1);
filter: brightness(1.2) drop-shadow(0 0 6px rgba(59, 130, 246, 0.5));
}
}
@keyframes libraryExpand {
0% {
transform: translateY(-100%) scale(0.8);
opacity: 0;
}
30% {
transform: translateY(-20%) scale(0.9);
opacity: 0.7;
}
60% {
transform: translateY(5%) scale(1.05);
opacity: 1;
}
80% {
transform: translateY(-2%) scale(1.02);
opacity: 1;
}
100% {
transform: translateY(0%) scale(1);
opacity: 1;
}
}
@keyframes templateMorph {
0% {
transform: scale(1) rotate(0deg);
}
25% {
transform: scale(1.05) rotate(-2deg) skewX(2deg);
}
50% {
transform: scale(1.1) rotate(2deg) skewX(-2deg);
}
75% {
transform: scale(1.05) rotate(-1deg) skewX(1deg);
}
100% {
transform: scale(1) rotate(0deg) skewX(0deg);
}
}
@keyframes communityRipple {
0% {
transform: translateX(-100%) scale(0.8);
opacity: 0;
}
30% {
transform: translateX(-20%) scale(0.9);
opacity: 0.7;
}
60% {
transform: translateX(5%) scale(1.05);
opacity: 1;
}
80% {
transform: translateX(-2%) scale(1.02);
opacity: 1;
}
100% {
transform: translateX(0%) scale(1);
opacity: 1;
}
}
@keyframes quizRoll {
0% {
transform: scale(1) rotate(0deg);
}
20% {
transform: scale(1.05) rotate(72deg);
}
40% {
transform: scale(1.1) rotate(144deg);
}
60% {
transform: scale(1.15) rotate(216deg);
}
80% {
transform: scale(1.1) rotate(288deg);
}
100% {
transform: scale(1) rotate(360deg);
}
}
@keyframes audienceGather {
0% {
transform: scale(1);
filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0));
}
25% {
transform: scale(0.95) scaleX(1.1);
filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3));
}
50% {
transform: scale(1.1) scaleX(0.9);
filter: brightness(1.1) drop-shadow(0 0 4px rgba(147, 51, 234, 0.5));
}
75% {
transform: scale(1.05) scaleX(1.05);
filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3));
}
100% {
transform: scale(1) scaleX(1);
filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0));
}
}
@keyframes setupConfigure {
0% {
transform: scale(1) rotate(0deg);
}
15% {
transform: scale(1.02) rotate(15deg);
}
30% {
transform: scale(1.04) rotate(-10deg);
}
45% {
transform: scale(1.06) rotate(20deg);
}
60% {
transform: scale(1.08) rotate(-15deg);
}
75% {
transform: scale(1.06) rotate(10deg);
}
90% {
transform: scale(1.02) rotate(-5deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
+23 -25
View File
@@ -1,33 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="theme-color" content="#333333" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="theme-color" content="#333333" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
<link rel="stylesheet" href="https://assets.cdn.clsrio.com/carbon.css" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
<script
defer
src="https://umami.hz.oncws.com/script.js"
data-website-id="80a9544a-4dda-4c91-b62f-b6be7a8a3b5c"
></script>
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
<script src="https://cdn.plyr.io/3.7.3/plyr.js"></script>
<script defer src="https://umami.hz.oncws.com/script.js"
data-website-id="80a9544a-4dda-4c91-b62f-b6be7a8a3b5c"></script>
%sveltekit.head%
</head>
<script src="https://cdn.plyr.io/3.7.3/plyr.js"></script>
<body data-sveltekit-preload-data="hover" class="">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" class="">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
@@ -23,7 +23,7 @@
import { snackbar } from '$lib/components/Snackbar/store';
import TabContent from '$lib/components/TabContent/index.svelte';
import Tabs from '$lib/components/Tabs/index.svelte';
import TextEditor from '$lib/components/TextEditor/index.svelte';
// import TextEditor from '$lib/components/TextEditor/index.svelte';
import MODES from '$lib/utils/constants/mode';
import { formatYoutubeVideo } from '$lib/utils/functions/formatYoutubeVideo';
import { supabase } from '$lib/utils/functions/supabase';
@@ -67,16 +67,27 @@
toggleMode = () => {}
}: Props = $props();
// let content = $state<EdraEditorProps['Content']>();
// let editor = $state<EdraEditorProps['Editor']>();
// function onUpdate() {
// content = editor?.getHTML();
// console.log('content updated:', content);
// if (mode === MODES.view) return;
// $lessonByTranslation[lessonId][$lesson.locale] = content;
// try {
// localStorage.setItem(`lesson-${lessonId}-${$lesson.locale}`, content);
// } catch (error) {}
// $isLessonDirty = true;
// }
let localeExists: Record<string, boolean> = {};
// let prevContent = '';
let timeoutId: NodeJS.Timeout;
let errors: Record<string, string> = {};
let editorWindowRef: Window | undefined = $state();
let aiButtonRef: HTMLDivElement | undefined = $state();
let openPopover = $state(false);
let player: HTMLVideoElement | undefined = $state();
let aiButtonClass =
'flex items-center gap-2 px-5 py-2 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md w-full mb-2';
// let editorWindowRef: Window | undefined = $state();
('flex items-center gap-2 px-5 py-2 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md w-full mb-2');
const lessonTitle = $derived($lesson.title);
@@ -324,7 +335,7 @@
initPlyr(player, $lesson.materials.videos);
});
let editorValue = $derived(
let _editorValue = $derived(
lessonFallbackNote($lesson.materials.note, $lessonByTranslation[lessonId], $lesson.locale)
);
</script>
@@ -403,7 +414,26 @@
</div>
<div class="mt-5 h-[60vh]">
<TextEditor
<!-- <div class="bg-background z-50 mt-12 size-full max-w-5xl rounded-md border border-dashed">
{#if editor && !editor.isDestroyed}
<EdraToolBar
class="bg-secondary/50 flex w-full items-center overflow-x-auto border-b border-dashed p-0.5"
{editor}
/>
{:else}
<div>
Debug: Editor state - {editor ? 'exists' : 'undefined'}, destroyed: {editor?.isDestroyed || 'N/A'}
</div>
{/if}
<EdraEditor
bind:editor
content={editorValue}
autofocus={true}
class="h-[300px] outline-none"
{onUpdate}
/>
</div> -->
<!-- <TextEditor
id={lessonId}
bind:editorWindowRef
value={editorValue}
@@ -419,7 +449,7 @@
$isLessonDirty = true;
}}
placeholder={$t('course.navItem.lessons.materials.tabs.note.placeholder')}
/>
/> -->
</div>
</TabContent>
@@ -79,7 +79,7 @@
<div class={className}>
<button
class="item relative flex items-center {btnPadding} mb-1 ml-2 {NavClasses.item} {isGroupActive &&
class="item group relative flex items-center {btnPadding} mb-1 ml-2 {NavClasses.item} {isGroupActive &&
!isLoading &&
NavClasses.active} w-[95%]"
tabindex="0"
@@ -1,15 +1,15 @@
<script lang="ts">
import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents'; // overview
import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents';
import BookCheckIcon from '@lucide/svelte/icons/book-check';
import ListTodoIcon from '@lucide/svelte/icons/list-todo';
import UsersIcon from '@lucide/svelte/icons/users';
import ScreenShareIcon from '@lucide/svelte/icons/screen-share';
import AwardIcon from '@lucide/svelte/icons/award';
import ScrollTextIcon from '@lucide/svelte/icons/scroll-text';
import SettingsIcon from '@lucide/svelte/icons/settings';
import MegaphoneIcon from '@lucide/svelte/icons/megaphone';
import ChartLineIcon from '@lucide/svelte/icons/chart-line';
import ListIcon from '@lucide/svelte/icons/list';
import SquareLibrary from '@lucide/svelte/icons/square-library';
import BookOpenCheck from '@lucide/svelte/icons/book-open-check';
import PanelTop from '@lucide/svelte/icons/panel-top';
import { NAV_IDS } from './constants';
interface Props {
@@ -20,25 +20,25 @@
</script>
{#if name === NAV_IDS.SECTION}
<TableOfContentsIcon size={16} />
<TableOfContentsIcon size={16} class="group-hover:animate-flip-book" />
{:else if name === NAV_IDS.NEWS_FEED}
<MegaphoneIcon size={16} />
<MegaphoneIcon size={16} class="group-hover:animate-shake-announcement" />
{:else if name === NAV_IDS.LESSONS}
<ScrollTextIcon size={16} />
<ScrollTextIcon size={16} class="group-hover:animate-scroll-unroll" />
{:else if name === NAV_IDS.ATTENDANCE}
<ListIcon size={16} />
<BookOpenCheck size={16} class="group-hover:animate-check-items" />
{:else if name === NAV_IDS.SUBMISSIONS}
<ListTodoIcon size={16} />
<SquareLibrary size={16} class="group-hover:animate-task-complete" />
{:else if name === NAV_IDS.MARKS}
<BookCheckIcon size={16} />
<BookCheckIcon size={16} class="group-hover:animate-grade-glow" />
{:else if name === NAV_IDS.PEOPLE}
<UsersIcon size={16} />
<UsersIcon size={16} class="group-hover:animate-people-wave" />
{:else if name === NAV_IDS.ANALYTICS}
<ChartLineIcon size={16} />
<ChartLineIcon size={16} class="group-hover:animate-chart-grow" />
{:else if name === NAV_IDS.LANDING_PAGE}
<ScreenShareIcon size={16} />
<PanelTop size={16} class="group-hover:animate-screen-glow" />
{:else if name === NAV_IDS.CERTIFICATES}
<AwardIcon size={16} />
<AwardIcon size={16} class="group-hover:animate-bounce" />
{:else if name === NAV_IDS.SETTINGS}
<SettingsIcon size={16} />
<SettingsIcon size={16} class="group-hover:animate-spin" />
{/if}
@@ -35,7 +35,7 @@
}
});
const result = (await response.json());
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || $t('courses.copy_course.error.failed_to_clone'));
@@ -34,11 +34,6 @@
emptyDescription = $t('courses.course_card.empty_description'),
isExplore = false
}: Props = $props();
export let courses: Course[] = [];
export let emptyTitle = $t('courses.course_card.empty_title');
export let emptyDescription = $t('courses.course_card.empty_description');
export let isExplore = false;
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
if (!progressRate || !totalLessons) {
@@ -55,19 +50,19 @@
try {
await deleteCourse($deleteCourseModal.id);
// Remove the course from the courses store
$coursesStore = $coursesStore.filter(course => course.id !== $deleteCourseModal.id);
$coursesStore = $coursesStore.filter((course) => course.id !== $deleteCourseModal.id);
// Show success message
snackbar.success('snackbar.course_deleted');
// Close modal and reset state
deleteCourseModal.set(deleteCourseModalInitialState);
} catch (error) {
console.error('Error deleting course:', error);
snackbar.error('snackbar.course_settings.error.went_wrong');
// Stop deleting state on error
$deleteCourseModal.isDeleting = false;
}
@@ -75,8 +70,8 @@
</script>
<CopyCourseModal />
<DeleteModal
onDelete={handleDeleteCourse}
<DeleteModal
onDelete={handleDeleteCourse}
bind:open={$deleteCourseModal.open}
isLoading={$deleteCourseModal.isDeleting}
/>
@@ -0,0 +1,6 @@
<script lang="ts">
import { Editor } from '@cio/ui/custom/editor';
import defaultContent from './default-content';
</script>
<Editor content={defaultContent} />
@@ -0,0 +1,825 @@
export default {
type: 'doc',
content: [
{
type: 'heading',
attrs: {
textAlign: 'center',
level: 1
},
content: [
{
type: 'text',
text: 'Introducing ClassroomIO'
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: 'justify',
class: null
},
content: [
{
type: 'text',
text: 'The AI native LMS. We have '
},
{
type: 'text',
marks: [
{
type: 'bold'
}
],
text: 'bold'
},
{
type: 'text',
text: ', '
},
{
type: 'text',
marks: [
{
type: 'italic'
}
],
text: 'italic'
},
{
type: 'text',
text: ', '
},
{
type: 'text',
marks: [
{
type: 'strike'
}
],
text: 'strike'
},
{
type: 'text',
text: ', '
},
{
type: 'text',
marks: [
{
type: 'underline'
}
],
text: 'underline'
},
{
type: 'text',
text: ', '
},
{
type: 'text',
marks: [
{
type: 'link',
attrs: {
href: 'https://github.com',
target: '_blank',
rel: 'noopener noreferrer',
class: null
}
}
],
text: 'link'
},
{
type: 'text',
text: ', '
},
{
type: 'text',
marks: [
{
type: 'code'
}
],
text: 'code'
},
{
type: 'text',
text: ', Superscript A'
},
{
type: 'text',
marks: [
{
type: 'superscript'
}
],
text: 'b'
},
{
type: 'text',
text: ', subscript A'
},
{
type: 'text',
marks: [
{
type: 'subscript'
}
],
text: 'b'
},
{
type: 'text',
text: ', '
},
{
type: 'text',
marks: [
{
type: 'highlight',
attrs: {
color: '#800080'
}
}
],
text: 'highlight1'
},
{
type: 'text',
marks: [
{
type: 'highlight',
attrs: {
color: '#1a1600'
}
}
],
text: ','
},
{
type: 'text',
text: ' '
},
{
type: 'text',
marks: [
{
type: 'highlight',
attrs: {
color: '#0000FF'
}
}
],
text: 'highlight2'
},
{
type: 'text',
text: ', '
},
{
type: 'text',
marks: [
{
type: 'textStyle',
attrs: {
color: '#008000',
fontSize: ''
}
}
],
text: 'textcolor1'
},
{
type: 'text',
marks: [
{
type: 'textStyle',
attrs: {
color: 'rgb(78, 211, 21)',
fontSize: ''
}
}
],
text: ', '
},
{
type: 'text',
marks: [
{
type: 'textStyle',
attrs: {
color: '#FFA500',
fontSize: ''
}
}
],
text: 'textcolor2'
}
]
},
{
type: 'blockquote',
content: [
{
type: 'paragraph',
attrs: {
textAlign: 'justify',
class: null
},
content: [
{
type: 'text',
text: 'This is a block Quote'
}
]
}
]
},
{
type: 'codeBlock',
attrs: {
language: 'javascript'
},
content: [
{
type: 'text',
text: 'console.log("Hello World")'
}
]
},
{
type: 'horizontalRule'
},
{
type: 'heading',
attrs: {
textAlign: 'left',
level: 2
},
content: [
{
type: 'text',
text: 'Support For Media Placeholders and Medias'
}
]
},
{
type: 'image-placeholder'
},
{
type: 'image',
attrs: {
src: 'https://placehold.co/800x400/6A00F5/white',
alt: null,
title: 'A Sample Image',
width: 658,
height: null,
align: 'center'
}
},
{
type: 'video-placeholder'
},
{
type: 'video',
attrs: {
src: 'https://videos.pexels.com/video-files/2491284/2491284-uhd_2732_1440_24fps.mp4',
alt: null,
title: 'A Sample Video',
width: 673,
height: null,
align: 'center'
}
},
{
type: 'audio-placeholder'
},
{
type: 'audio',
attrs: {
src: 'https://cdn.pixabay.com/audio/2024/10/30/audio_42e6870f29.mp3',
alt: null,
title: 'A Sample Audio',
width: 670,
height: null,
align: 'center'
}
},
{
type: 'horizontalRule'
},
{
type: 'heading',
attrs: {
textAlign: null,
level: 2
},
content: [
{
type: 'text',
text: 'IFrames'
}
]
},
{
type: 'iframe-placeholder'
},
{
type: 'iframe',
attrs: {
src: 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3748.5645078258503!2d75.17451197505285!3d20.026784381383496!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3bdb93bd138ae4bd%3A0x574c6482cf0b89cf!2sEllora%20Caves!5e0!3m2!1sen!2sin!4v1742466514831!5m2!1sen!2sin',
alt: null,
title: 'Ellora Caves, Maharastra, India',
width: 621,
height: null,
align: 'center'
}
},
{
type: 'iframe',
attrs: {
src: 'https://www.youtube.com/embed/jfKfPfyJRdk?si=SYBE0PkI5v79-54W',
alt: null,
title: 'Lofi Hip Hop Youtube Streaming Video',
width: 623,
height: null,
align: 'center'
}
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
}
},
{
type: 'horizontalRule'
},
{
type: 'heading',
attrs: {
textAlign: null,
level: 2
},
content: [
{
type: 'text',
text: 'Typography'
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Smilipicker, type '
},
{
type: 'text',
marks: [
{
type: 'code'
}
],
text: ':)'
},
{
type: 'text',
text: ' and it becomes 🙂 or 😉. Typography support e.g. type '
},
{
type: 'text',
marks: [
{
type: 'code'
}
],
text: '!='
},
{
type: 'text',
text: ' and it becomes ≠. Similarly (c) becomes ©, -> becomes → and many more like 1×2, ½. We also have color visualizer. #FFF, #000, #FF00FF can be visualized.'
}
]
},
{
type: 'horizontalRule'
},
{
type: 'heading',
attrs: {
textAlign: null,
level: 2
},
content: [
{
type: 'text',
text: 'Tables'
}
]
},
{
type: 'table',
content: [
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: [323],
style: null
},
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Hello World'
}
]
}
]
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: [328],
style: null
},
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Hello World'
}
]
}
]
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: [330],
style: null
},
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Hello World'
}
]
}
]
}
]
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: [323],
style: null
},
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Hello World'
}
]
}
]
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: [328],
style: null
},
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Hello World'
}
]
}
]
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: [330],
style: null
},
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Hello World'
}
]
}
]
}
]
}
]
},
{
type: 'heading',
attrs: {
textAlign: null,
level: 3
},
content: [
{
type: 'text',
marks: [
{
type: 'textStyle',
attrs: {
color: 'var(--tw-prose-headings)',
fontSize: '1.5em'
}
}
],
text: 'Rending Math and '
},
{
type: 'inlineMath',
attrs: {
latex: '\\LaTeX',
evaluate: 'no',
display: 'no'
}
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'The editor supports the '
},
{
type: 'inlineMath',
attrs: {
latex: '\\LaTeX',
evaluate: 'no',
display: 'no'
}
},
{
type: 'text',
text: ' rendering. e.g.'
}
]
},
{
type: 'orderedList',
attrs: {
start: 1
},
content: [
{
type: 'listItem',
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Functions like, '
},
{
type: 'inlineMath',
attrs: {
latex: 'sin²\\theta + cos²\\theta=1, a²+b²=c²',
evaluate: 'no',
display: 'no'
}
}
]
}
]
},
{
type: 'listItem',
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Matrix like unit matrix'
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'inlineMath',
attrs: {
latex: '\\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}',
evaluate: 'no',
display: 'no'
}
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'or more matrices like'
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'inlineMath',
attrs: {
latex:
'\\begin{pmatrix} 1 & 0 & \\cdots & 0 \\\\ 0 & 1 & \\cdots & 0 \\\\ \\vdots & \\vdots & \\ddots & \\vdots \\\\ 0 & 0 & \\cdots & 1 \\end{pmatrix}',
evaluate: 'no',
display: 'no'
}
}
]
}
]
},
{
type: 'listItem',
content: [
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'text',
text: 'Integrals and differential'
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'inlineMath',
attrs: {
latex:
'\\int_{0}^{\\frac{\\pi}{2}} \\sin(x) \\,dx = \\left[-\\cos(x)\\right]_0^{\\frac{\\pi}{2}} = -\\cos\\left(\\frac{\\pi}{2}\\right) + \\cos(0)',
evaluate: 'no',
display: 'no'
}
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
},
content: [
{
type: 'inlineMath',
attrs: {
latex: '\\frac{\\mathrm{d}}{\\mathrm{d}x}(\\sin x) = \\cos x',
evaluate: 'no',
display: 'no'
}
}
]
}
]
}
]
},
{
type: 'heading',
attrs: {
textAlign: null,
level: 3
},
content: [
{
type: 'text',
text: 'Explore More…'
}
]
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
}
},
{
type: 'paragraph',
attrs: {
textAlign: null,
class: null
}
}
]
};
@@ -11,11 +11,6 @@
}
let { open = $bindable(false), onDelete = () => {}, isLoading = false }: Props = $props();
function handleDelete() {
onDelete();
open = false;
}
</script>
<Modal
@@ -1,17 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import BellIcon from '@lucide/svelte/icons/bell';
import MoonIcon from '@lucide/svelte/icons/moon';
import SunIcon from '@lucide/svelte/icons/sun';
import MenuIcon from '@lucide/svelte/icons/menu';
import XIcon from '@lucide/svelte/icons/x';
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
import { ModeSwitcher } from '@cio/ui/base/dark-mode';
import { IconButton } from '$lib/components/IconButton';
import { globalStore } from '$lib/utils/store/app';
import { currentOrgPath } from '$lib/utils/store/org';
import { toggleBodyByMode } from '$lib/utils/functions/app';
import { sideBar } from '../Org/store';
import { t } from '$lib/utils/functions/translations';
@@ -27,16 +24,6 @@
$sideBar.hidden = !$sideBar.hidden;
};
function toggleDarkMode() {
$globalStore.isDark = !$globalStore.isDark;
toggleBodyByMode($globalStore.isDark);
if (browser) {
localStorage.setItem('mode', $globalStore.isDark ? 'dark' : '');
}
}
let coursesPath = $derived(
$globalStore.isOrgSite ? '/lms/mylearning' : isCoursePage ? `${$currentOrgPath}/courses` : $currentOrgPath
);
@@ -91,13 +78,14 @@
<BellIcon class="custom text-white" size={16} />
</li>
<li>
<IconButton size="small" onClick={toggleDarkMode}>
<!-- <IconButton size="small" onClick={toggleDarkMode}>
{#if $globalStore.isDark}
<SunIcon size={16} class="custom" />
{:else}
<MoonIcon class="custom text-white" size={16} />
{/if}
</IconButton>
</IconButton> -->
<ModeSwitcher />
</li>
<li></li>
@@ -4,6 +4,7 @@
import { isCoursePage } from '$lib/utils/functions/app';
import { t } from '$lib/utils/functions/translations';
import type { TCustomLinks } from './types';
import { ModeSwitcher } from '@cio/ui/base/dark-mode';
import Logo from './Logo.svelte';
import CustomLinks from './CustomLinks.svelte';
@@ -76,6 +77,8 @@
{:else if !isOrgSite && !$page.url.pathname?.includes('/404')}
<AuthButtons {disableSignup} {redirect} />
{/if}
<ModeSwitcher />
</ul>
{#if showLinks}
@@ -1,9 +1,10 @@
<script>
import { Popover } from 'carbon-components-svelte';
import Menu from './Menu.svelte';
import Modal from '$lib/components/Modal/index.svelte';
import { isMobile } from '$lib/utils/store/useMobile';
import { profileMenu } from '../store';
import { clickOutside } from '$lib/utils/functions/routes/dashboard';
</script>
{#if $isMobile}
@@ -16,15 +17,12 @@
>
<Menu />
</Modal>
{:else}
<Popover
bind:open={$profileMenu.open}
align="right-bottom"
caret={true}
on:click:outside={({ detail }) => {
$profileMenu.open = !!$profileMenu.ref?.contains(detail.target);
}}
{:else if $profileMenu.open}
<div
use:clickOutside
on:click:outside={() => ($profileMenu.open = false)}
class="absolute bottom-28 left-0 z-[300] w-full border bg-black p-2 dark:bg-white"
>
<Menu />
</Popover>
</div>
{/if}
@@ -76,10 +76,11 @@
if (error) throw error;
} catch (error) {
let message = error instanceof Error ? error.message : `${error}`;
if (message.includes('profile_username_key')) {
message = $t('snackbar.lms.error.username_exists');
}
snackbar.error(`${$t('snackbar.lms.error.update')}: ${message}`);
loading = false;
@@ -5,7 +5,7 @@
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
import UsersIcon from '@lucide/svelte/icons/users';
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
import SettingsIcon from '@lucide/svelte/icons/settings';
import Settings2 from '@lucide/svelte/icons/settings-2';
import MessageSquareMoreIcon from '@lucide/svelte/icons/message-square-more';
import BadgeHelpIcon from '@lucide/svelte/icons/badge-help';
import Dice6Icon from '@lucide/svelte/icons/dice-6';
@@ -98,24 +98,24 @@
{#if menuItem.show}
<a href="{$currentOrgPath}{menuItem.path}" class="text-black no-underline" onclick={toggleSidebar}>
<li
class="mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {menuItem.isActive
class="group mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {menuItem.isActive
? NavClasses.active
: 'dark:text-white'}"
>
{#if menuItem.path === ''}
<LayoutDashboardIcon size={16} />
<LayoutDashboardIcon size={16} class="group-hover:animate-gather" />
{:else if menuItem.path === '/courses'}
<LibraryBigIcon size={16} />
<LibraryBigIcon size={16} class="group-hover:animate-library-expand" />
{:else if menuItem.path === '/site'}
<LayoutTemplateIcon size={16} />
<LayoutTemplateIcon size={16} class="group-hover:animate-template-morph" />
{:else if menuItem.path === '/community'}
<MessageSquareMoreIcon size={16} />
<MessageSquareMoreIcon size={16} class="group-hover:animate-ripple" />
{:else if menuItem.path === '/quiz'}
<Dice6Icon size={16} />
<Dice6Icon size={16} class="group-hover:animate-roll" />
{:else if menuItem.path === '/audience'}
<UsersIcon size={16} />
<UsersIcon size={16} class="group-hover:animate-gather" />
{:else if menuItem.path === '/setup'}
<SettingsIcon size={16} />
<Settings2 size={16} class="group-hover:animate-setup-configure" />
{/if}
<p class="text-sm font-medium">{menuItem.label}</p>
</li>
@@ -52,3 +52,19 @@ export const formatDate = (_selectedDate: string | undefined): string => {
return selectedDate.format('ddd, MMM D');
};
export function clickOutside(node) {
const handleClick = (event) => {
if (node && !node.contains(event.target) && !event.defaultPrevented) {
node.dispatchEvent(new CustomEvent('click:outside', { detail: event.target }));
}
};
document.addEventListener('click', handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
};
}
@@ -158,9 +158,7 @@ export const orgLandingpageValidation = (fields = {}) => {
export const onboardingValidation = (fields = {}, step) => {
const onboardingValidationSchema = {
stepOne: z.object({
fullname: z
.string()
.min(5, { message: `${t.get('validations.onboarding.step_one.full_name.min_char')}` }),
fullname: z.string().min(5, { message: `${t.get('validations.onboarding.step_one.full_name.min_char')}` }),
orgName: getOrgNameValidation(),
siteName: getSiteNameValidation()
}),
@@ -178,8 +176,7 @@ export const onboardingValidation = (fields = {}, step) => {
})
};
const schema =
step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
const schema = step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
const { error } = schema.safeParse(fields);
return processErrors(error);
+15 -5
View File
@@ -13,7 +13,13 @@
// import PageLoadProgressBar from '$lib/components/Progress/PageLoadProgressBar.svelte';
import Snackbar from '$lib/components/Snackbar/index.svelte';
import UpgradeModal from '$lib/components/Upgrade/Modal.svelte';
import { isCoursesPage, isLMSPage, isOrgPage, toggleBodyByMode } from '$lib/utils/functions/app';
import {
isCoursesPage,
isLMSPage,
isOrgPage
// toggleBodyByMode
} from '$lib/utils/functions/app';
import { getProfile, setupAnalytics } from '$lib/utils/functions/appSetup';
import hideNavByRoute from '$lib/utils/functions/routes/hideNavByRoute';
import { getSupabase } from '$lib/utils/functions/supabase';
@@ -28,7 +34,9 @@
import { onMount } from 'svelte';
import { MetaTags } from 'svelte-meta-tags';
import '../app.postcss';
import { ModeWatcher } from '@cio/ui/base/dark-mode';
import '../app.css';
let { data, children } = $props();
@@ -52,8 +60,8 @@
if (browser) {
// Update theme - dark or light mode
$globalStore.isDark = localStorage.getItem('mode') === 'dark';
toggleBodyByMode($globalStore.isDark);
// $globalStore.isDark = localStorage.getItem('mode') === 'dark';
// toggleBodyByMode($globalStore.isDark);
if (data.isOrgSite && data.org?.theme) {
setTheme(data.org?.theme);
@@ -116,9 +124,11 @@
<svelte:window onresize={handleResize} />
<ModeWatcher />
<MetaTags {...metaTags} />
<Theme bind:theme={carbonTheme} />
<!-- <Theme bind:theme={carbonTheme} /> -->
<UpgradeModal />
+8 -6
View File
@@ -9,6 +9,7 @@
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { getSupabase } from '$lib/utils/functions/supabase';
import { validateEmail } from '$lib/utils/functions/validateEmail';
import Edra from '$lib/components/Edra/EdraRoot.svelte';
let email = $state('');
let isAdding = $state(false);
@@ -57,7 +58,7 @@
<title>ClassroomIO - The classroom software</title>
</svelte:head>
<div class="m-2 flex w-screen flex-col items-center justify-center font-sans sm:m-0 md:h-[93vh]">
<div class="m-2 flex w-screen flex-col items-center justify-center font-sans sm:m-0 md:min-h-[93vh] dark:bg-black">
<!--
<img
src="/logo-192.png"
@@ -73,8 +74,9 @@
<p class="text-center text-lg dark:text-white">The operating system for classroooms of the future 🚀🚀🚀.</p>
</div>
<Edra />
<form onsubmit={preventDefault(handleSubmit)} class="my-4 hidden">
<div class="flex flex-col items-center sm:flex-row">
<div class="flex hidden flex-col items-center sm:flex-row">
{#if success}
<p class="dark:text-white">You have been added successfully. Thanks for joining.</p>
{:else}
@@ -91,18 +93,18 @@
</div>
</form>
<div class="mt-4 flex flex-col md:flex-row">
<div class="mt-4 flex md:flex-row">
{#each areas as area, index}
<div
class="active hover:border-primary-700 m-3 max-w-[350px] rounded-md border-2 bg-white px-12 py-3 shadow-xl dark:bg-black {animate}"
>
<h3 class="text-3xl dark:text-white">
{#if index === 0}
<DoorOpenIcon size={16} />
<DoorOpenIcon size={30} />
{:else if index === 1}
<ChartAreaIcon size={16} />
<ChartAreaIcon size={30} />
{:else if index === 2}
<BotIcon size={16} />
<BotIcon size={30} />
{/if}
{area.title}
</h3>
@@ -29,8 +29,8 @@
import { LANGUAGES } from '$lib/utils/constants/translation';
import { t } from '$lib/utils/functions/translations';
import { profile } from '$lib/utils/store/user';
import { COURSE_VERSION, type Lesson, type LessonCompletion } from '$lib/utils/types';
import { Dropdown } from 'carbon-components-svelte';
import { COURSE_VERSION, LOCALE, type Lesson, type LessonCompletion } from '$lib/utils/types';
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
let { data = $bindable() } = $props();
@@ -248,7 +248,20 @@
</IconButton>
</div>
<Dropdown items={LANGUAGES} bind:selectedId={$lesson.locale} class="h-full" />
<div>
<DropdownMenu.Root>
<DropdownMenu.Trigger>Open</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
{#each LANGUAGES as lang}
<DropdownMenu.Item onclick={() => ($lesson.locale = lang.id as LOCALE)}>
{lang.text}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
{/if}
</RoleBasedSecurity>
</div>
@@ -137,7 +137,7 @@
</div>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600">
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600 dark:text-white">
<h3 class="mt-0 text-2xl font-bold">
{$t('dashboard.top_courses')}
</h3>
@@ -189,7 +189,7 @@
</div>
</div>
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600">
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600 dark:text-white">
<h3 class="mt-0 text-2xl font-bold">
{$t('dashboard.recent_enrollments')}
</h3>
+7 -2
View File
@@ -12,12 +12,17 @@ const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [vitePreprocess({})],
kit: {
adapter: useNodeAdapter ? adapterNode() : adapterVercel(),
alias: {
$lib: path.resolve('./src/lib'),
$mail: path.resolve('./src/mail')
$mail: path.resolve('./src/mail'),
'$src/tools': path.resolve('../../packages/ui/src/tools/index.ts'),
'$src/base/*': path.resolve('../../packages/ui/src/base/*'),
'@cio/ui': path.resolve('../../packages/ui/src'),
'@cio/ui/*': path.resolve('../../packages/ui/src/*'),
'@cio/api': path.resolve('../api/dist'),
'@cio/api/*': path.resolve('../api/dist/*')
}
}
};
-9
View File
@@ -1,9 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'class',
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')]
};
+2 -6
View File
@@ -1,7 +1,6 @@
import { defineConfig, loadEnv } from 'vite';
import fs from 'fs';
import path from 'path';
import { sveltekit } from '@sveltejs/kit/vite';
export default ({ mode }) => {
@@ -25,15 +24,12 @@ export default ({ mode }) => {
include: ['@cio/api/rpc-types']
},
resolve: {
mainFields: ['browser'],
alias: {
'@cio/api': path.resolve(__dirname, '../api/dist')
}
mainFields: ['browser']
}
});
};
function getServer(params: any) {
function getServer(params) {
const { VITE_USE_HTTPS_ON_LOCALHOST } = params || {};
if (VITE_USE_HTTPS_ON_LOCALHOST === 'true') {
return {