feat: [lw] free tools (#297)

* checkpoint

* checkpoint

* feat: Create free tools page

* responsiveness fix

* rename free-tools to tools

* add slug

* feat: Random name picker

* quick fix

* PR Fixes

* PR Fixes

* - adjustments

* update env example

* remove base url

* remove base url in .env.example

* fix type errors

* add og images

* add signup cta to /tools layout

* update cta

* add socials avatar

* add meta tags

* feat: tic tac toe (#307)

* Feat: tic tac toe

* PR Adjustments

* feat: Implement Timed Activity Tool (#316)

* chore: update verification email content

* chore: landing page improvements

* feat: grade exercise with AI (#296)

* working on the UI of the grading

* working on the grading function of the radio and checkboxes

* ai grading fully implemented with dummy functions for generating scores

* working on the openai response

* working on the openai response

* finished with the ai_grading

* finished with the ai_grading

* creating new migration file

* working on reviews

* updated the translations

* improvements

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* feat: selfhost tinymce

* fix: logo resolution error in manifest.json

* feat: move from gpt3.5 to gpt-4o

* feat: customize the LMS from dashboard (#304)

* Done with the customization of the LMS task

* generated custom_lms migration file

* updated the migration file

* copy improvements

* update wording

* made updates based on reviews,i hope i didnt miss anything

* updated the translations and migration files

* refactoring

* disabled improvements for apps

* more improvements

* more custom improvements

* pr refactoring

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* fix: CustomizeLMS not found

* fix: CustomizeLms wrong path

* setup

* docs: update short hero tagline

* debug: courses not showing on vercel

* more debugging

* i think this fixes it

* fix: phew, clean up debug cause it works

* fix: move RBS into coursecontainer on marks page

* chore: cleaner way to implement redirect if role permission denied

* docs: update tagline

* fix: delete /org/# route

* checkpoint

* add reusable header component

* fix: empty lesson title bug (#314)

* finished the lesson title bug

* made chnages based on review

* Update +page.svelte

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* feat: add sound cdn

* remove console.log

* feat: course types - self paced and live class (#309)

* working on the course type

* trying to mark it persist

* almost done here

* fixed sendig emtpy options bug

* created migration file

* created migration file

* made updates based on PR review

* merge

* update course type card

* add redirect logic with classroomio.com/new

* make new course modal only open/close with url

* feat: page active state (#315)

* finished the active link state

* updated the active link state based on review

* simplify code

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* add type to course card

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* general improvements to free tools

* fix timer and styling issues

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
Co-authored-by: Em_Dev <Chifez1@gmail.com>

* update tagline for tools page

* feat: Add Free Section to Landing Page (#332)

* feat: Add Free Section to Landing Page

* PR Fixes

* refactor

* PR Fixes

* restructure

* improve cursor UX

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* feat: Progress Report Tool (#329)

* checkpoint

* fix: corrections

* finalization

* improve ux of progress tool

* more styling improvements

* PR Fixes

* PR Fixes

* fix: mood bg on report

* fix: generate exercise with AI bug (#331)

* fix: enable lesson cascade delete (#335)

* styling improvements

* chore: hide course grading toggle in settings (#338)

* feat: reset select in community page (#339)

* Feat: Add Reset for comminity dropdown

* - add translation

* refactor

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* fix: prevent hypen at start or end of organzation or domain name (#340)

* fix: adding hypen to start or end of org or domain name

* fix: add org name validation to settings page

* chore: remove unnecessary variable use

* chore: remove error reset function

* Update OrgSettings.svelte

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* fix: full screen

* fix: pnpm version error & add top level .nvmrc (#343)

* update name of tool

* chore: add translations for validation messages (#345)

* chore: add validation texts to locale file

* feat: switch all validations from plain text to use i18n locale

* feat: switch review form validations from plain text to use locale file

* run translation script

* chore: add translations for validation messages (#345) (#347)

* chore: add translations for validation messages (#345)

* chore: add validation texts to locale file

* feat: switch all validations from plain text to use i18n locale

* feat: switch review form validations from plain text to use locale file

* run translation script

---------

Co-authored-by: Iniubong Obonguko <iniubongobonguko2018@gmail.com>

* PR Fixes

* Bug fixes on mobile

* Final bug fix

* code improvements

* PR Fixes

* Merge Fix

* PR Fixes

* fix: width

* code cleanup

* feat: Promodo Tool (tracks tim spent)

* feat: Pomodoro timer (without tracking feature)

* fix:
- change pause functionality to next
- correct spelling of pomodoro

* fix: pomodoro logo rename

* styling improvements

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
Co-authored-by: Em_Dev <Chifez1@gmail.com>
Co-authored-by: Iniubong Obonguko <iniubongobonguko2018+github@gmail.com>
Co-authored-by: Iniubong Obonguko <iniubongobonguko2018@gmail.com>
Co-authored-by: Aditya Deshlahre <132184385+adityadeshlahre@users.noreply.github.com>

* feat: Implement Bottom menu (#341)

* popup logic done

* popup UI first phase done

* finished the menupop up for both lms and admin

* finished the menupop up for both lms and admin

* done

* cleanup

* fixed popover position

* still working on the routing

* i think that is it for the day

* updated the menupopup currentorgdomain

* rename popup to bottomMenu

* refactoring

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* remove logout

* change tagline

* feat: Store todolist to localStorage (#375)

* feat: Save todo to localStorage

* remove fly animation to todos

* feat:  Progress Report avatar update (#376)

* feat: Reorder Avatar list

* quick fix

* change avatar url

* more improvements

* quick background fix

* fix: white background

* body background fix

* update lock

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
Co-authored-by: Em_Dev <Chifez1@gmail.com>
Co-authored-by: Iniubong Obonguko <iniubongobonguko2018+github@gmail.com>
Co-authored-by: Iniubong Obonguko <iniubongobonguko2018@gmail.com>
Co-authored-by: Aditya Deshlahre <132184385+adityadeshlahre@users.noreply.github.com>
This commit is contained in:
Adeyemi Adetunji
2024-09-30 01:36:12 +01:00
committed by GitHub
co-authored by Rotimi Best Em_Dev Iniubong Obonguko Iniubong Obonguko Aditya Deshlahre
parent 91bd9d4414
commit 9c066c79be
106 changed files with 3778 additions and 478 deletions
@@ -1,30 +0,0 @@
<script lang="ts">
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { supabase } from '$lib/utils/functions/supabase';
import { capturePosthogEvent } from '$lib/utils/services/posthog';
import { currentOrg, orgs, defaultCurrentOrgState } from '$lib/utils/store/org';
import { user, profile, defaultProfileState, defaultUserState } from '$lib/utils/store/user';
import { t } from '$lib/utils/functions/translations';
import posthog from 'posthog-js';
import { goto } from '$app/navigation';
async function logout() {
const { error } = await supabase.auth.signOut();
if (error) {
console.error('Error logging out: ', error);
}
currentOrg.set(defaultCurrentOrgState);
orgs.set([]);
user.set(defaultUserState);
profile.set(defaultProfileState);
capturePosthogEvent('user_logged_out');
posthog.reset();
goto('/login');
}
</script>
<PrimaryButton variant={VARIANTS.OUTLINED} label={$t('settings.profile.logout')} onClick={logout} />
@@ -3,16 +3,17 @@
import HelpIcon from 'carbon-icons-svelte/lib/Help.svelte';
import LicenseDraft from 'carbon-icons-svelte/lib/LicenseDraft.svelte';
import Explore from 'carbon-icons-svelte/lib/Explore.svelte';
import Settings from 'carbon-icons-svelte/lib/Settings.svelte';
import HomeIcon from '$lib/components/Icons/HomeIcon.svelte';
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
import CommunityIcon from '$lib/components/Icons/CommunityIcon.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import { profile } from '$lib/utils/store/user';
import { NavClasses } from '$lib/utils/constants/reusableClass';
import { sideBar } from '$lib/components/Org/store';
import { profileMenu, sideBar } from '$lib/components/Org/store';
import { t } from '$lib/utils/functions/translations';
import { currentOrg } from '$lib/utils/store/org';
import { ChevronRight } from 'carbon-icons-svelte';
import ProfileMenu from '$lib/components/Org/ProfileMenu/index.svelte';
interface SideLinks {
name: string;
@@ -73,70 +74,87 @@
};
</script>
<aside
class={`${
$sideBar.hidden
? '-translate-x-[100%] absolute md:translate-x-0 md:relative z-40'
: 'translate-x-0 absolute md:relative z-40'
} overflow-y-auto transition w-[250px] min-w-[250px] bg-gray-100 dark:bg-neutral-900 h-[calc(100vh-48px)]`}
>
<div class="h-full flex flex-col">
<div class="border-b border-gray-200 dark:border-neutral-600 pt-5 px-4">
<div class="w-full flex flex-col items-center">
<Avatar
src={$profile.avatar_url}
name={$profile.fullname}
shape="rounded-full"
width="w-20"
height="h-20"
/>
<div bind:this={$profileMenu.ref} class="static md:relative">
<aside
class={`${
$sideBar.hidden
? '-translate-x-[100%] absolute md:translate-x-0 md:relative z-40 top-[48px] md:top-0'
: 'translate-x-0 absolute md:relative z-40 top-[48px] md:top-0'
} overflow-y-auto transition w-[250px] min-w-[250px] bg-gray-100 dark:bg-neutral-900 h-[calc(100vh-48px)]`}
>
<div class="h-full flex flex-col">
<div class="border-b border-gray-200 dark:border-neutral-600 pt-5 px-4">
<div class="w-full flex flex-col items-center">
<Avatar
src={$profile.avatar_url}
name={$profile.fullname}
shape="rounded-full"
width="w-20"
height="h-20"
/>
<div class="mt-5 flex justify-center w-full">
<p
class="dark:text-white text-lg font-bold whitespace-nowrap truncate max-w-[80%] text-center"
>
{$profile.fullname}
</p>
</div>
</div>
<ul class="my-5">
{#each sideLinks as item (item.link)}
<a href={item.link} class="text-black" on:click={toggleSidebar}>
<li
class="flex items-center py-3 px-4 mb-2 {NavClasses.item} {isActive(
$page.url.pathname,
`${item.link}`
)
? NavClasses.active
: 'dark:text-white'}"
<div class="mt-5 flex justify-center w-full">
<p
class="dark:text-white text-lg font-bold whitespace-nowrap truncate max-w-[80%] text-center"
>
<svelte:component this={item.icon} size={24} class="carbon-icon dark:fill-[#fff]" />
<p class="dark:text-white ml-2">{item.name}</p>
</li>
</a>
{/each}
{$profile.fullname}
</p>
</div>
</div>
<ul class="my-5">
{#each sideLinks as item (item.name)}
<a href={item.link} class="text-black" on:click={toggleSidebar}>
<li
class="flex items-center py-3 px-4 mb-2 {NavClasses.item} {isActive(
$page.url.pathname,
`${item.link}`
)
? NavClasses.active
: 'dark:text-white'}"
>
<svelte:component this={item.icon} size={24} class="carbon-icon dark:fill-[#fff]" />
<p class="dark:text-white ml-2">{item.name}</p>
</li>
</a>
{/each}
</ul>
</div>
<span class="flex-grow" />
<ul class="my-5 pb-5 px-4">
<a href="/lms" class="text-black" on:click={toggleSidebar}>
<li class="flex items-center py-3 px-4 mb-2 rounded">
<HelpIcon size={20} class="carbon-icon dark:text-white" />
<p class="dark:text-white ml-2">{$t('lms_navigation.help')}</p>
</li>
</a>
<button
class="w-full"
on:click={() => {
$profileMenu.open = !$profileMenu.open;
$sideBar.hidden = true;
}}
>
<div
class="text-black no-underline cursor-pointer flex items-center justify-between mb-2 px-2.5 py-1.5 w-full {NavClasses.item}"
>
<div class="flex text-start items-center justify-start space-x-1">
<Avatar
src={$profile.avatar_url}
name={$profile.username}
width="w-[1.2rem]"
height="h-[1.2rem]"
/>
<p class="text-sm font-medium truncate max-w-full">{$profile.fullname}</p>
</div>
<div>
<ChevronRight />
</div>
</div>
</button>
</ul>
</div>
<span class="flex-grow" />
<ul class="my-5 pb-5 px-4">
<a href="/lms" class="text-black" on:click={toggleSidebar}>
<li class="flex items-center py-3 px-4 mb-2 rounded">
<HelpIcon size={20} class="carbon-icon dark:text-white" />
<p class="dark:text-white ml-2">{$t('lms_navigation.help')}</p>
</li>
</a>
<a href="/lms/settings" class="text-black" on:click={toggleSidebar}>
<li
class="flex items-center py-3 px-4 mb-2 {NavClasses.item} {isActive(
$page.url.pathname,
`/lms/settings`
) && NavClasses.active}"
>
<Settings size={20} class="carbon-icon dark:text-white" />
<p class="dark:text-white ml-2">{$t('lms_navigation.settings')}</p>
</li>
</a>
</ul>
</div>
</aside>
</aside>
<ProfileMenu />
</div>
@@ -7,7 +7,6 @@
import { supabase } from '$lib/utils/functions/supabase';
import { profile } from '$lib/utils/store/user';
import { snackbar } from '$lib/components/Snackbar/store';
import LogoutButton from '$lib/components/Buttons/Logout/index.svelte';
import generateUUID from '$lib/utils/functions/generateUUID';
import { t } from '$lib/utils/functions/translations';
import LanguagePicker from '$lib/components/Org/Settings/LanguagePicker.svelte';
@@ -120,6 +119,5 @@
isDisabled={loading}
onClick={handleUpdate}
/>
<LogoutButton />
</Row>
</Grid>
@@ -0,0 +1,165 @@
<script>
import { globalStore } from '$lib/utils/store/app';
import { user, profile, defaultProfileState, defaultUserState } from '$lib/utils/store/user';
import {
currentOrg,
orgs,
defaultCurrentOrgState,
currentOrgDomain,
currentOrgPath
} from '$lib/utils/store/org';
import { ChevronDown, Settings } from 'carbon-icons-svelte';
import Logout from 'carbon-icons-svelte/lib/Logout.svelte';
import Rocket from 'carbon-icons-svelte/lib/Rocket.svelte';
import NewTab from 'carbon-icons-svelte/lib/NewTab.svelte';
import { goto } from '$app/navigation';
import { profileMenu } from '../store';
import { supabase } from '$lib/utils/functions/supabase';
import { capturePosthogEvent } from '$lib/utils/services/posthog';
import { t } from '$lib/utils/functions/translations';
import posthog from 'posthog-js';
async function logout() {
const { error } = await supabase.auth.signOut();
if (error) {
console.error('Error logging out: ', error);
}
currentOrg.set(defaultCurrentOrgState);
orgs.set([]);
user.set(defaultUserState);
profile.set(defaultProfileState);
capturePosthogEvent('user_logged_out');
posthog.reset();
closeMenu();
goto('/login');
}
function closeMenu() {
$profileMenu.open = false;
}
</script>
<div class="md:px-4 md:py-4 py-2 px-2 rounded-md cursor-pointer">
<div class="border-b py-3 space-y-4">
<p class="text-xs font-semibold text-gray-500">{$t('profileMenu.profile')}</p>
<a
href={`${!$globalStore.isOrgSite ? $currentOrgPath : '/lms'}/settings`}
class="flex items-center justify-between hover:no-underline"
on:click={closeMenu}
>
<span class="flex items-center gap-2 max-w-[70%]">
<img src={$profile.avatar_url} alt="profile" class="h-8 w-8 rounded-full" />
<div>
<p class="text-sm font-semibold w-[80%] truncate">{$profile.fullname}</p>
<p class="text-xs w-[80%] truncate">{$profile.email}</p>
</div>
</span>
<div>
<Settings size={20} />
</div>
</a>
</div>
{#if !$globalStore.isOrgSite}
<div class="border-b py-3 space-y-4">
<p class="text-xs font-semibold text-gray-500">{$t('profileMenu.current_org')}</p>
<a
href={`${$currentOrgPath}/settings?tab=org`}
class="flex items-center justify-between hover:no-underline"
on:click={closeMenu}
>
<span class="flex items-center gap-2 max-w-[70%]">
<div
class="flex items-center justify-center h-8 w-8 p-4 bg-blue-900 text-white font-semibold rounded-lg"
>
{$currentOrg.shortName}
</div>
<div>
<p class="text-sm font-semibold w-[80%] truncate">{$currentOrg.name}</p>
<p class="text-xs w-[80%] truncate">
{$currentOrgDomain}
</p>
</div>
</span>
<div>
<Settings size={20} />
</div>
</a>
</div>
{/if}
<div class="border-b py-3 space-y-4">
<p class="text-xs font-semibold text-gray-500">{$t('profileMenu.free_tools')}</p>
<a
href="https://classroomio.com/tools/progress"
target="_blank"
class="flex items-center gap-2"
on:click={closeMenu}
>
<img src="/progress.svg" alt="progress" class="h-6 w-6 rounded-full" />
<p class="text-sm font-semibold">{$t('profileMenu.progress')}</p>
</a>
<a
href="https://classroomio.com/tools/activity-stopwatch"
target="_blank"
class=" flex items-center gap-2"
on:click={closeMenu}
>
<img src="/timer.svg" alt="timer" class="h-6 w-6 rounded-full" />
<p class="text-sm font-semibold">{$t('profileMenu.timer')}</p>
</a>
<a
href="https://classroomio.com/tools/tic-tac-toe"
target="_blank"
class=" flex items-center gap-2"
on:click={closeMenu}
>
<img src="/tictac.svg" alt="tic_tac_toe" class="h-6 w-6 rounded-full" />
<p class="text-sm font-semibold">{$t('profileMenu.tic_tac')}</p>
</a>
<a
href="https://classroomio.com/tools"
on:click={closeMenu}
target="_blank"
class="flex ml-auto w-fit items-center justify-end"
>
<div class="text-blue-900 font-semibold text-xs flex items-center gap-1">
{$t('profileMenu.see_more')}
<ChevronDown class="text-blue-900" />
</div>
</a>
</div>
{#if !$globalStore.isOrgSite}
<div class="border-b py-3 space-y-4">
<a
href="https://classroomio.com/roadmap"
target="_blank"
on:click={closeMenu}
class="hover:no-underline flex items-center gap-2"
>
<NewTab />
<p class="text-sm font-semibold">{$t('profileMenu.whats_new')}</p>
</a>
<a
href="https://classroomio.com/blog/launch-week"
target="_blank"
on:click={closeMenu}
class="hover:no-underline flex items-center gap-2"
>
<Rocket />
<p class="text-sm font-semibold">{$t('profileMenu.launch_week')}</p>
</a>
</div>
{/if}
<button on:click={logout} class="w-full pt-3 space-y-4">
<span class="flex items-center gap-2">
<Logout />
<p class="text-sm font-semibold">{$t('settings.profile.logout')}</p>
</span>
</button>
</div>
@@ -0,0 +1,31 @@
<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';
</script>
{#if $isMobile}
<Modal
bind:open={$profileMenu.open}
onClose={() => ($profileMenu.open = false)}
width="w-4/5"
containerClass="h-full !max-h-[70vh] pt-0 pb-2"
headerClass="py-1"
>
<Menu />
</Modal>
{:else}
<Popover
bind:open={$profileMenu.open}
align="right-bottom"
caret={true}
on:click:outside={({ detail }) => {
console.log('on:click:outside');
$profileMenu.open = !!$profileMenu.ref?.contains(detail.target);
}}
>
<Menu />
</Popover>
{/if}
@@ -8,7 +8,6 @@
import { supabase } from '$lib/utils/functions/supabase';
import { profile } from '$lib/utils/store/user';
import { snackbar } from '$lib/components/Snackbar/store';
import LogoutButton from '$lib/components/Buttons/Logout/index.svelte';
import generateUUID from '$lib/utils/functions/generateUUID';
import { t } from '$lib/utils/functions/translations';
import LanguagePicker from './LanguagePicker.svelte';
@@ -129,6 +128,5 @@
isDisabled={loading}
onClick={handleUpdate}
/>
<LogoutButton />
</Row>
</Grid>
@@ -21,24 +21,21 @@
function getSelectedByTab(tabKey = '') {
const tab = tabs.find((t) => t.tabKey === tabKey);
return tab ? tab.key : 0;
}
function changeRouteOnTabChange(key = 0) {
function onTabChange(e) {
const key = e.detail;
const tab = tabs.find((t) => t.key === key);
if (tab) {
return goto(tab.href);
goto(tab.href);
}
}
onMount(() => {
$: if (browser) {
query = new URLSearchParams($page.url.search);
tabKey = query.get('tab') || '';
selected = getSelectedByTab(tabKey);
});
$: {
if (browser) {
changeRouteOnTabChange(selected);
}
}
$: {
@@ -82,7 +79,7 @@
}
</script>
<Tabs autoWidth bind:selected>
<Tabs autoWidth bind:selected on:change={onTabChange}>
{#each tabs as tab}
<Tab label={tab.label} href={tab.href} disabled={tab.disabled} />
{/each}
@@ -2,7 +2,7 @@
import { page } from '$app/stores';
import HelpIcon from 'carbon-icons-svelte/lib/Help.svelte';
import ForumIcon from 'carbon-icons-svelte/lib/Forum.svelte';
import { SettingsAdjust } from 'carbon-icons-svelte';
import { ChevronRight, SettingsAdjust } from 'carbon-icons-svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import OrgSelector from '$lib/components/OrgSelector/OrgSelector.svelte';
import HomeIcon from '$lib/components/Icons/HomeIcon.svelte';
@@ -11,13 +11,15 @@
import SiteSettingsIcon from '$lib/components/Icons/SiteSettingsIcon.svelte';
import AudienceIcon from '$lib/components/Icons/AudienceIcon.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import { currentOrgPath, currentOrgPlan, isFreePlan } from '$lib/utils/store/org';
import { currentOrgPath, isFreePlan } from '$lib/utils/store/org';
import { isOrgAdmin } from '$lib/utils/store/org';
import { profile } from '$lib/utils/store/user';
import { NavClasses } from '$lib/utils/constants/reusableClass';
import { sideBar } from './store';
import { sideBar, profileMenu } from './store';
import { t } from '$lib/utils/functions/translations';
import { goto } from '$app/navigation';
import ProfileMenu from '$lib/components/Org/ProfileMenu/index.svelte';
interface menuItems {
label: string;
@@ -45,43 +47,6 @@
goto(window.location.pathname + '?upgrade=true');
};
// $: {
// menuItems = [
// {
// path: '',
// label: 'Dashboard',
// show: true
// },
// // {
// // path: '/quiz',
// // label: 'Quizzes'
// // },
// {
// path: '/courses',
// label: 'Courses',
// show: true
// },
// {
// path: '/community',
// label: 'Community',
// show: true
// },
// // {
// // path: '/site',
// // label: 'Site settings',
// // },
// {
// path: '/audience',
// label: 'Audience',
// show: true
// },
// {
// path: '/setup',
// label: 'Setup',
// show: $isOrgAdmin
// }
// ];
// }
$: menuItems = [
{
path: '',
@@ -111,99 +76,110 @@
];
</script>
<aside
class={`${
$sideBar.hidden
? 'absolute z-40 -translate-x-[100%] md:relative md:translate-x-0'
: 'absolute z-40 translate-x-0 md:relative'
} border-r-1 h-[calc(100vh-48px)] w-[250px] min-w-[250px] overflow-y-auto border border-b-0 border-l-0 border-t-0 border-gray-100 dark:border-neutral-600 bg-gray-100 transition dark:bg-neutral-900`}
>
<div class="flex h-full flex-col">
<div class="">
<OrgSelector />
<div bind:this={$profileMenu.ref} class="static md:relative">
<aside
class={`${
$sideBar.hidden
? 'absolute z-40 -translate-x-[100%] md:relative md:translate-x-0 top-[48px] md:top-0'
: 'absolute z-40 translate-x-0 md:relative top-[48px] md:top-0'
} border-r-1 h-[calc(100vh-48px)] w-[250px] min-w-[250px] overflow-y-auto border border-b-0 border-l-0 border-t-0 border-gray-100 dark:border-neutral-600 bg-gray-100 transition dark:bg-neutral-900`}
>
<div class="flex h-full flex-col">
<div class="">
<OrgSelector />
<ul class="mt-4 my-2 px-4">
{#each menuItems as menuItem}
{#if menuItem.show}
<a
href="{$currentOrgPath}{menuItem.path}"
class="text-black no-underline"
on:click={toggleSidebar}
>
<li
class="mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {isActive(
$page.url.pathname,
`${$currentOrgPath}${menuItem.path}`
)
? NavClasses.active
: 'dark:text-white'}"
<ul class="mt-4 my-2 px-4">
{#each menuItems as menuItem}
{#if menuItem.show}
<a
href="{$currentOrgPath}{menuItem.path}"
class="text-black no-underline"
on:click={toggleSidebar}
>
{#if menuItem.path === ''}
<HomeIcon />
{:else if menuItem.path === '/courses'}
<CourseIcon />
{:else if menuItem.path === '/site'}
<SiteSettingsIcon />
{:else if menuItem.path === '/community'}
<ForumIcon size={20} class="carbon-icon fill-[#000] dark:fill-[#fff]" />
{:else if menuItem.path === '/quiz'}
<QuizIcon />
{:else if menuItem.path === '/audience'}
<AudienceIcon />
{:else if menuItem.path === '/setup'}
<SettingsAdjust />
{/if}
<p class="text-sm font-medium">{menuItem.label}</p>
</li>
</a>
{/if}
{/each}
<li
class="mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {isActive(
$page.url.pathname,
`${$currentOrgPath}${menuItem.path}`
)
? NavClasses.active
: 'dark:text-white'}"
>
{#if menuItem.path === ''}
<HomeIcon />
{:else if menuItem.path === '/courses'}
<CourseIcon />
{:else if menuItem.path === '/site'}
<SiteSettingsIcon />
{:else if menuItem.path === '/community'}
<ForumIcon size={20} class="carbon-icon fill-[#000] dark:fill-[#fff]" />
{:else if menuItem.path === '/quiz'}
<QuizIcon />
{:else if menuItem.path === '/audience'}
<AudienceIcon />
{:else if menuItem.path === '/setup'}
<SettingsAdjust />
{/if}
<p class="text-sm font-medium">{menuItem.label}</p>
</li>
</a>
{/if}
{/each}
</ul>
</div>
<span class="flex-grow" />
{#if $isFreePlan}
<div
class="border-primary-700 mx-4 flex flex-col items-center justify-center gap-4 rounded-md border px-2 py-6 text-center hover:scale-95 transition-all ease-in-out"
>
<img src="/upgrade.png" alt="upgrade" class="h-16 w-16" />
<span class="flex flex-col gap-1">
<p class="text-base font-semibold">{$t('org_navigation.early_adopter')}</p>
<p class="text-xs">{$t('org_navigation.unlock')}</p>
</span>
<PrimaryButton
label={$t('org_navigation.upgrade')}
onClick={openModal}
className="font-normal"
/>
</div>
{/if}
<ul class="my-5 px-4 pb-5">
<a href={$currentOrgPath} class="text-black no-underline" on:click={toggleSidebar}>
<li class="mb-2 flex items-center rounded px-2.5 py-1.5">
<HelpIcon size={20} class="carbon-icon dark:text-white" />
<p class="ml-2.5 dark:text-white text-sm font-medium">{$t('org_navigation.help')}</p>
</li>
</a>
<button
class="w-full"
on:click={() => {
$profileMenu.open = !$profileMenu.open;
$sideBar.hidden = true;
}}
>
<div
class="text-black no-underline cursor-pointer flex items-center justify-between gap-2.5 px-2.5 py-2 mb-2 {NavClasses.item}"
>
<div class="flex text-start items-center justify-start space-x-1 w-full">
<Avatar
src={$profile.avatar_url}
name={$profile.username}
width="w-[1.2rem]"
height="h-[1.2rem]"
/>
<p class="text-sm font-medium truncate max-w-full">{$profile.fullname}</p>
</div>
<div>
<ChevronRight />
</div>
</div>
</button>
</ul>
</div>
<span class="flex-grow" />
</aside>
{#if $isFreePlan}
<div
class="border-primary-700 mx-4 flex flex-col items-center justify-center gap-4 rounded-md border px-2 py-6 text-center hover:scale-95 transition-all ease-in-out"
>
<img src="/upgrade.png" alt="upgrade" class="h-16 w-16" />
<span class="flex flex-col gap-1">
<p class="text-base font-semibold">{$t('org_navigation.early_adopter')}</p>
<p class="text-xs">{$t('org_navigation.unlock')}</p>
</span>
<PrimaryButton
label={$t('org_navigation.upgrade')}
onClick={openModal}
className="font-normal"
/>
</div>
{/if}
<ul class="my-5 px-4 pb-5">
<a href={$currentOrgPath} class="text-black no-underline" on:click={toggleSidebar}>
<li class="mb-2 flex items-center rounded px-2.5 py-1.5">
<HelpIcon size={20} class="carbon-icon dark:text-white" />
<p class="ml-2.5 dark:text-white text-sm font-medium">{$t('org_navigation.help')}</p>
</li>
</a>
<a href="{$currentOrgPath}/settings" class="text-black no-underline" on:click={toggleSidebar}>
<li
class="mb-2 flex items-center px-2.5 py-1.5 {NavClasses.item} {isActive(
$page.url.pathname,
`${$currentOrgPath}/settings`
)
? NavClasses.active
: 'dark:text-white'}"
>
<Avatar
src={$profile.avatar_url}
name={$profile.username}
width="w-[1.2rem]"
height="h-[1.2rem]"
/>
<p class="ml-2.5 text-sm font-medium">{$t('org_navigation.settings')}</p>
</li>
</a>
</ul>
</div>
</aside>
<ProfileMenu />
</div>
@@ -7,3 +7,11 @@ export const newOrgModal = writable({
export const sideBar = writable({
hidden: true
});
export const profileMenu = writable<{
ref?: HTMLDivElement;
open: boolean;
}>({
ref: undefined,
open: false
});
@@ -1326,6 +1326,17 @@
"specialization": {
"course_tag": "Spezialisierung"
},
"profileMenu": {
"profile": "PROFIL",
"current_org": "AKTUELLE ORGANISATION",
"free_tools": "KOSTENLOSE WERKZEUGE",
"progress": "Fortschrittsbericht",
"timer": "Aktivitätstimer",
"tic_tac": "Tictac-Zehe",
"see_more": "Mehr sehen",
"whats_new": "Was ist neu",
"launch_week": "Startwoche"
},
"validations": {
"invalid_type_error": "Darf nicht leer sein",
"email": {
@@ -49,7 +49,18 @@
"forgot": "Forgot password?",
"login": "Log In"
},
"profileMenu": {
"profile": "PROFILE",
"current_org": "CURRENT ORGANIZATION",
"free_tools": "FREE TOOLS",
"progress": "Progress report",
"timer": "Activity timer",
"tic_tac": "Tictac toe",
"see_more": "See more",
"whats_new": "What's new",
"launch_week": "Launch week"
},
"settings": {
"heading": "Settings",
"subheadings": {
@@ -1326,6 +1326,17 @@
"specialization": {
"course_tag": "Especialización"
},
"profileMenu": {
"profile": "PERFIL",
"current_org": "ORGANIZACIÓN ACTUAL",
"free_tools": "HERRAMIENTAS GRATUITAS",
"progress": "Informe de progreso",
"timer": "Temporizador de actividad",
"tic_tac": "dedo del pie tres en raya",
"see_more": "Ver más",
"whats_new": "Qué hay de nuevo",
"launch_week": "Semana de lanzamiento"
},
"validations": {
"invalid_type_error": "No debe estar vacío",
"email": {
@@ -1326,6 +1326,17 @@
"specialization": {
"course_tag": "Spécialisation"
},
"profileMenu": {
"profile": "PROFIL",
"current_org": "ORGANISATION ACTUELLE",
"free_tools": "OUTILS GRATUITS",
"progress": "Rapport d'étape",
"timer": "Minuterie d'activité",
"tic_tac": "Tic-tac",
"see_more": "Voir plus",
"whats_new": "Quoi de neuf",
"launch_week": "Semaine de lancement"
},
"validations": {
"invalid_type_error": "Ne doit pas être vide",
"email": {
@@ -1326,6 +1326,17 @@
"specialization": {
"course_tag": "विशेषज्ञता"
},
"profileMenu": {
"profile": "प्रोफ़ाइल",
"current_org": "वर्तमान संघ",
"free_tools": "मुफ़्त उपकरण",
"progress": "प्रगति रिपोर्ट",
"timer": "गतिविधि टाइमर",
"tic_tac": "टिक टीएसी को पैर की अंगुली",
"see_more": "और देखें",
"whats_new": "नया क्या है",
"launch_week": "लॉन्च सप्ताह"
},
"validations": {
"invalid_type_error": "खाली नहीं होना चाहिए",
"email": {
@@ -1326,6 +1326,17 @@
"specialization": {
"course_tag": "Especialização"
},
"profileMenu": {
"profile": "PERFIL",
"current_org": "ORGANIZAÇÃO ATUAL",
"free_tools": "FERRAMENTAS GRATUITAS",
"progress": "Relatório de progresso",
"timer": "Temporizador de atividade",
"tic_tac": "Jogo da velha",
"see_more": "Ver mais",
"whats_new": "O que há de novo",
"launch_week": "Semana de lançamento"
},
"validations": {
"invalid_type_error": "Não deve estar vazio",
"email": {
@@ -1326,6 +1326,17 @@
"specialization": {
"course_tag": "Специализация"
},
"profileMenu": {
"profile": "ПРОФИЛЬ",
"current_org": "ТЕКУЩАЯ ОРГАНИЗАЦИЯ",
"free_tools": "БЕСПЛАТНЫЕ ИНСТРУМЕНТЫ",
"progress": "Отчет о ходе работы",
"timer": "Таймер активности",
"tic_tac": "Крестики-нолики",
"see_more": "Узнать больше",
"whats_new": "Что нового",
"launch_week": "Неделя запуска"
},
"validations": {
"invalid_type_error": "Не должно быть пустым",
"email": {
@@ -1326,6 +1326,17 @@
"specialization": {
"course_tag": "Chuyên môn"
},
"profileMenu": {
"profile": "HỒ SƠ",
"current_org": "TỔ CHỨC HIỆN TẠI",
"free_tools": "CÔNG CỤ MIỄN PHÍ",
"progress": "Báo cáo tiến trình",
"timer": "Hẹn giờ hoạt động",
"tic_tac": "TIC Tac Toe",
"see_more": "Xem thêm",
"whats_new": "Có gì mới",
"launch_week": "Tuần ra mắt"
},
"validations": {
"invalid_type_error": "Không được để trống",
"email": {