feat: add custom navigation link to org landing page (#569)
* feat: custom navbar UI complete * chore: added translations * fear: attending to reviews * add orgname to org landing page --------- Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
co-authored by
Rotimi Best
parent
9a7018eb34
commit
27ee51155e
+54
@@ -0,0 +1,54 @@
|
||||
<script>
|
||||
import YoutubeVideo from './YoutubeVideo.svelte';
|
||||
import UploadVideo from './UploadVideo.svelte';
|
||||
import EmbedLink from './EmbedLink.svelte';
|
||||
import * as CONSTANTS from './constants';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { isFreePlan } from '$lib/utils/store/org';
|
||||
import FlashFilled from 'carbon-icons-svelte/lib/FlashFilled.svelte';
|
||||
|
||||
export let lessonId = '';
|
||||
|
||||
const tabs = CONSTANTS.videoTabs;
|
||||
let currentTab = tabs[0].value;
|
||||
|
||||
const onChange = (tab) => () => (currentTab = tab);
|
||||
</script>
|
||||
|
||||
<section class="flex h-full w-full flex-col items-start gap-3 md:flex-row">
|
||||
<div class="flex flex-row items-center gap-2 md:flex-col">
|
||||
<p class="mb-3 w-full text-start text-sm font-normal text-[#4F4B4B] dark:text-[#b0a9a9]">
|
||||
{$t('course.navItem.lessons.materials.tabs.video.add_video.add_by')}
|
||||
</p>
|
||||
{#each tabs as item (item.value)}
|
||||
<button
|
||||
on:click={onChange(item.value)}
|
||||
class={`my-1 w-full border px-4 py-3 ${
|
||||
currentTab === item.value
|
||||
? 'border border-[#0233BD] bg-[#F5F8FE] dark:text-black'
|
||||
: 'border border-gray-200 dark:border-neutral-600'
|
||||
} flex cursor-pointer flex-row items-center justify-start gap-2 whitespace-nowrap rounded-md`}
|
||||
>
|
||||
{#if $isFreePlan && item.value === 3}
|
||||
<FlashFilled size={20} class="text-blue-700" />
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={item.icon}
|
||||
size={20}
|
||||
color={`${currentTab === item.value ? 'dark:invert-0' : 'dark:invert'}`}
|
||||
/>
|
||||
{/if}
|
||||
<p>{$t(item.title)}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<main class="h-full w-full">
|
||||
{#if currentTab === 1}
|
||||
<YoutubeVideo />
|
||||
{:else if currentTab === 2}
|
||||
<EmbedLink />
|
||||
{:else}
|
||||
<UploadVideo {lessonId} />
|
||||
{/if}
|
||||
</main>
|
||||
</section>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
export let disableSignup = false;
|
||||
export let redirect = '';
|
||||
export let isMobile = false;
|
||||
export let onMobileClick: (() => void) | undefined = undefined;
|
||||
|
||||
function handleLogin() {
|
||||
if (onMobileClick) onMobileClick();
|
||||
goto('/login' + redirect);
|
||||
}
|
||||
|
||||
function handleSignup() {
|
||||
if (onMobileClick) onMobileClick();
|
||||
goto('/signup' + redirect);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex {isMobile ? 'flex-col items-center justify-center space-y-3' : ''}">
|
||||
<li class={isMobile ? '' : 'block'}>
|
||||
<div class="flex">
|
||||
<PrimaryButton
|
||||
label={$t('navigation.login')}
|
||||
variant={VARIANTS.TEXT}
|
||||
onClick={handleLogin}
|
||||
className={isMobile ? 'w-full justify-center' : ''}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{#if !disableSignup}
|
||||
<li class={isMobile ? '' : 'block'}>
|
||||
<div class="flex">
|
||||
<PrimaryButton
|
||||
label={$t('navigation.signup')}
|
||||
variant={VARIANTS.CONTAINED}
|
||||
onClick={handleSignup}
|
||||
className={isMobile ? 'w-full justify-center' : ''}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import type { TCustomLinks } from './types';
|
||||
|
||||
export let customLinks: TCustomLinks | undefined = undefined;
|
||||
export let isMobile = false;
|
||||
export let onMobileClick: (() => void) | undefined = undefined;
|
||||
|
||||
function handleLinkClick() {
|
||||
if (onMobileClick) onMobileClick();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if customLinks}
|
||||
<div class={isMobile ? 'space-y-4' : 'mr-4 hidden items-center space-x-6 lg:flex'}>
|
||||
{#each customLinks.links as link}
|
||||
<li>
|
||||
<a
|
||||
href={link.url}
|
||||
target={link.openInNewTab ? '_blank' : '_self'}
|
||||
rel={link.openInNewTab ? 'noopener noreferrer' : ''}
|
||||
class={isMobile
|
||||
? 'hover:text-primary-600 block rounded-md px-3 py-2 font-medium text-gray-700 transition-colors duration-200 hover:bg-gray-50'
|
||||
: 'hover:text-primary-600 rounded-md px-4 py-2 font-medium text-gray-700 no-underline transition-all duration-200 ease-in-out hover:bg-gray-100'}
|
||||
on:click={handleLinkClick}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
export let logo: string | undefined;
|
||||
export let orgName: string | undefined;
|
||||
</script>
|
||||
|
||||
<div class="logo">
|
||||
<a
|
||||
href="/"
|
||||
title={`${$t('navigation.goto')} ${orgName || 'ClassroomIO'} ${$t('navigation.home')}`}
|
||||
id="logo"
|
||||
data-hveid="8"
|
||||
>
|
||||
<img
|
||||
src={logo || '/logo-192.png'}
|
||||
alt={`${orgName || 'ClassroomIO'} logo`}
|
||||
class="mx-auto inline-block w-9 rounded"
|
||||
data-atf="1"
|
||||
/>
|
||||
{#if orgName}
|
||||
<span class="line-clamp-1 hidden text-sm font-medium md:block">{orgName}</span>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/utils/store/user';
|
||||
import { type TCustomLinks } from './types';
|
||||
import CustomLinks from './CustomLinks.svelte';
|
||||
import AuthButtons from './AuthButtons.svelte';
|
||||
|
||||
export let mobileMenuOpen = false;
|
||||
export let customLinks: TCustomLinks | undefined = undefined;
|
||||
export let disableSignup = false;
|
||||
export let redirect = '';
|
||||
|
||||
let mobileMenuRef: HTMLElement;
|
||||
|
||||
function closeMobileMenu() {
|
||||
mobileMenuOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Mobile Menu Overlay -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
{#if mobileMenuOpen}
|
||||
<div
|
||||
class="mobile-menu-overlay fixed inset-0 z-50 bg-black bg-opacity-50 lg:hidden"
|
||||
on:click={closeMobileMenu}
|
||||
>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
bind:this={mobileMenuRef}
|
||||
class="mobile-menu-sidebar absolute right-0 top-0 h-full w-64 transform bg-white shadow-lg transition-transform duration-300 ease-in-out"
|
||||
on:click|stopPropagation
|
||||
>
|
||||
<div class="flex items-center justify-between border-b border-gray-200 p-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800">Menu</h3>
|
||||
<button
|
||||
on:click={closeMobileMenu}
|
||||
class="rounded-md p-2 text-gray-500 transition-colors duration-200 hover:bg-gray-100 hover:text-gray-700"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul class="space-y-4 divide-y-[1px]">
|
||||
<CustomLinks {customLinks} isMobile={true} onMobileClick={closeMobileMenu} />
|
||||
|
||||
<!-- Login/Signup buttons in mobile menu -->
|
||||
{#if !$user.isLoggedIn && !$page.url.pathname?.includes('/404')}
|
||||
<li class="pt-4">
|
||||
<AuthButtons
|
||||
{disableSignup}
|
||||
{redirect}
|
||||
isMobile={true}
|
||||
onMobileClick={closeMobileMenu}
|
||||
/>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* Mobile menu styles */
|
||||
.mobile-menu-overlay {
|
||||
animation: fadeIn 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.mobile-menu-sidebar {
|
||||
animation: slideIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,119 +1,81 @@
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/utils/store/user';
|
||||
import { isCoursePage } from '$lib/utils/functions/app';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import type { TCustomLinks } from './types';
|
||||
|
||||
import Logo from './Logo.svelte';
|
||||
import CustomLinks from './CustomLinks.svelte';
|
||||
import AuthButtons from './AuthButtons.svelte';
|
||||
import MobileMenu from './MobileMenu.svelte';
|
||||
|
||||
export let disableSignup = false;
|
||||
export let logo;
|
||||
export let orgName;
|
||||
export let logo: string | undefined = undefined;
|
||||
export let orgName: string | undefined = undefined;
|
||||
export let isOrgSite = false;
|
||||
export let backgroundColor = 'bg-white dark:bg-black';
|
||||
export let customLinks: TCustomLinks | undefined = undefined;
|
||||
|
||||
let navClass = '';
|
||||
let mobileMenuOpen = false;
|
||||
|
||||
const redirect = isCoursePage($page.url.pathname) ? `?redirect=${$page.url.pathname}` : '';
|
||||
$: redirect = isCoursePage($page.url.pathname) ? `?redirect=${$page.url.pathname}` : '';
|
||||
$: showLinks =
|
||||
customLinks && customLinks.show && customLinks.links && customLinks.links.length > 0;
|
||||
|
||||
$: navClass = '';
|
||||
function toggleMobileMenu() {
|
||||
mobileMenuOpen = !mobileMenuOpen;
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav
|
||||
class="{navClass} {backgroundColor} sticky top-0 z-50 flex w-full border-b border-l-0 border-r-0 border-t-0 border-gray-300 px-2 py-1"
|
||||
>
|
||||
<ul class="flex w-full items-center">
|
||||
<div class="logo">
|
||||
<a
|
||||
href="/"
|
||||
title={`${$t('navigation.goto')} ${orgName || 'ClassroomIO'} ${$t('navigation.home')}`}
|
||||
data-hveid="8"
|
||||
>
|
||||
<img
|
||||
src={logo || '/logo-192.png'}
|
||||
alt={`${orgName || 'ClassroomIO'} logo`}
|
||||
class="mx-auto inline-block w-9 rounded"
|
||||
data-atf="1"
|
||||
/>
|
||||
{#if orgName}
|
||||
<span class="line-clamp-1 hidden text-sm font-medium md:block">{orgName}</span>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#if $user.isLoggedIn}
|
||||
<li class="hidden">
|
||||
<a class="block" href="dashboard"> {$t('navigation.dashboard')} </a>
|
||||
</li>
|
||||
|
||||
<li class="hidden">
|
||||
<a class="block" href="courses"> {$t('navigation.courses')} </a>
|
||||
</li>
|
||||
|
||||
<li class="hidden">
|
||||
<a class="block" href="discussions"> {$t('navigation.discussion')} </a>
|
||||
</li>
|
||||
|
||||
<li class="hidden">
|
||||
<a class="block" href="people">{$t('navigation.people')} </a>
|
||||
</li>
|
||||
{/if}
|
||||
<Logo {logo} {orgName} />
|
||||
|
||||
<span class="flex-grow" />
|
||||
|
||||
<!-- Mobile Menu Button - Only show when custom links exist -->
|
||||
{#if isOrgSite && showLinks}
|
||||
<button
|
||||
class="mobile-menu-btn hover:text-primary-600 rounded-md p-2 text-gray-700 transition-colors duration-200 hover:bg-gray-100 lg:hidden"
|
||||
on:click={toggleMobileMenu}
|
||||
aria-label="Toggle mobile menu"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if customLinks && showLinks}
|
||||
<CustomLinks {customLinks} />
|
||||
{/if}
|
||||
|
||||
{#if $user.isLoggedIn}
|
||||
{#if isOrgSite}
|
||||
<li><a class="block" href="/lms"> {$t('navigation.goto_lms')} </a></li>
|
||||
{/if}
|
||||
{:else if !$page.url.pathname?.includes('/404')}
|
||||
<li>
|
||||
<div class="flex">
|
||||
<PrimaryButton
|
||||
label={$t('navigation.login')}
|
||||
variant={VARIANTS.TEXT}
|
||||
onClick={() => goto('/login' + redirect)}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
{#if !disableSignup}
|
||||
<div class="flex">
|
||||
<PrimaryButton
|
||||
label={$t('navigation.signup')}
|
||||
variant={VARIANTS.CONTAINED}
|
||||
onClick={() => goto('/signup' + redirect)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
{:else if isOrgSite && !$page.url.pathname?.includes('/404')}
|
||||
<!-- Hide login/signup buttons on mobile when custom links exist -->
|
||||
<div class="hidden lg:block">
|
||||
<AuthButtons {disableSignup} {redirect} />
|
||||
</div>
|
||||
{:else if !isOrgSite && !$page.url.pathname?.includes('/404')}
|
||||
<AuthButtons {disableSignup} {redirect} />
|
||||
{/if}
|
||||
<!-- <li class="hidden">
|
||||
<a
|
||||
class="flex items-center"
|
||||
href="https://t.me/classroomio"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt="File:Telegram logo.svg"
|
||||
class="mr-1"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Telegram_alternative_logo.svg/50px-Telegram_alternative_logo.svg.png"
|
||||
decoding="async"
|
||||
width="25"
|
||||
height="25"
|
||||
srcset="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Telegram_alternative_logo.svg/50px-Telegram_alternative_logo.svg.png 1.5x, https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Telegram_logo.svg/1024px-Telegram_logo.svg.png 2x"
|
||||
data-file-width="25"
|
||||
data-file-height="25"
|
||||
/>
|
||||
<span><SendAlt32 class="carbon-icon dark:text-white" /> Канал</span>
|
||||
<SendAlt32 class="carbon-icon dark:text-white" />
|
||||
<span>Канал</span>
|
||||
</a>
|
||||
</li> -->
|
||||
<!-- <li class="new-question">
|
||||
<NewQuestion />
|
||||
</li> -->
|
||||
</ul>
|
||||
|
||||
{#if showLinks}
|
||||
<MobileMenu bind:mobileMenuOpen {customLinks} {disableSignup} {redirect} />
|
||||
{/if}
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
@@ -122,17 +84,12 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* clearfix */
|
||||
ul::after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* li.new-question {
|
||||
margin-top: 10px;
|
||||
} */
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--main-primary-color);
|
||||
@@ -141,9 +98,15 @@
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1002px) {
|
||||
/* Mobile menu button styles */
|
||||
.mobile-menu-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1023px) {
|
||||
nav.hide {
|
||||
display: none;
|
||||
}
|
||||
@@ -154,5 +117,9 @@
|
||||
a {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export type CustomLink = {
|
||||
id: number;
|
||||
label: string;
|
||||
url: string;
|
||||
openInNewTab: boolean;
|
||||
};
|
||||
|
||||
export type TCustomLinks = {
|
||||
show: boolean;
|
||||
links: CustomLink[];
|
||||
};
|
||||
|
||||
export type TNavigationProps = {
|
||||
disableSignup?: boolean;
|
||||
logo?: string;
|
||||
orgName?: string;
|
||||
isOrgSite?: boolean;
|
||||
backgroundColor?: string;
|
||||
customLinks?: TCustomLinks | null;
|
||||
};
|
||||
@@ -177,6 +177,13 @@
|
||||
<PageLoader />
|
||||
{:else}
|
||||
<main>
|
||||
<Navigation
|
||||
logo={org.avatar_url}
|
||||
orgName={org.name}
|
||||
disableSignup={true}
|
||||
isOrgSite={true}
|
||||
customLinks={$landingPageSettings.customLinks}
|
||||
/>
|
||||
<!-- Header Section -->
|
||||
{#if $landingPageSettings.header.show}
|
||||
<header
|
||||
@@ -188,13 +195,6 @@
|
||||
}`}
|
||||
style="background-image: {getBgImage($landingPageSettings)}"
|
||||
>
|
||||
<Navigation
|
||||
logo={org.avatar_url}
|
||||
orgName={org.name}
|
||||
disableSignup={true}
|
||||
isOrgSite={true}
|
||||
/>
|
||||
|
||||
<div class="absolute top-0 z-10 h-[100vh] w-full bg-white opacity-80 md:h-[90vh]" />
|
||||
{#if $landingPageSettings.header.banner.show}
|
||||
<div class="flex items-center justify-center py-2 md:h-full">
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
let isSaving = false;
|
||||
let creatingNewQuestion = false;
|
||||
let creatingNewCustomLink = false;
|
||||
let hasUnsavedChanges = false;
|
||||
let widgetKey = '';
|
||||
const banner = [
|
||||
@@ -40,6 +41,12 @@
|
||||
content: ''
|
||||
};
|
||||
|
||||
let newCustomLink = {
|
||||
label: '',
|
||||
url: '',
|
||||
openInNewTab: false
|
||||
};
|
||||
|
||||
function widgetControl(key: string) {
|
||||
widgetKey = key;
|
||||
$handleOpenWidget.open = true;
|
||||
@@ -77,6 +84,40 @@
|
||||
$landingPageSettings.faq.questions = filteredFaq;
|
||||
}
|
||||
|
||||
function createNewCustomLink() {
|
||||
newCustomLink = {
|
||||
label: '',
|
||||
url: '',
|
||||
openInNewTab: false
|
||||
};
|
||||
creatingNewCustomLink = true;
|
||||
}
|
||||
|
||||
function saveNewCustomLink() {
|
||||
if (newCustomLink.label !== '' && newCustomLink.url !== '') {
|
||||
$landingPageSettings.customLinks.links = [
|
||||
...$landingPageSettings.customLinks.links,
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
label: newCustomLink.label,
|
||||
url: newCustomLink.url,
|
||||
openInNewTab: newCustomLink.openInNewTab
|
||||
}
|
||||
];
|
||||
creatingNewCustomLink = false;
|
||||
}
|
||||
}
|
||||
|
||||
function cancelNewCustomLink() {
|
||||
creatingNewCustomLink = false;
|
||||
}
|
||||
|
||||
function deleteCustomLink(id: number) {
|
||||
let customLinks = $landingPageSettings.customLinks.links;
|
||||
const filteredCustomLinks = customLinks.filter((link) => link.id !== id);
|
||||
$landingPageSettings.customLinks.links = filteredCustomLinks;
|
||||
}
|
||||
|
||||
const checkPrefix = (inputValue: string) => {
|
||||
if (!inputValue) return;
|
||||
|
||||
@@ -103,7 +144,7 @@
|
||||
const message = error?.message || 'snackbar.lms.error.try_again';
|
||||
snackbar.error(`snackbar.lms.error.update ${message}`);
|
||||
} else {
|
||||
$currentOrg.landingpage = $landingPageSettings;
|
||||
$currentOrg.landingpage = $landingPageSettings as any;
|
||||
snackbar.success('snackbar.success_update');
|
||||
hasUnsavedChanges = false;
|
||||
}
|
||||
@@ -124,7 +165,7 @@
|
||||
|
||||
$landingPageSettings = {
|
||||
...landingpage
|
||||
};
|
||||
} as OrgLandingPageJson;
|
||||
}
|
||||
}
|
||||
$: setDefault($currentOrg?.landingpage as unknown as OrgLandingPageJson);
|
||||
@@ -578,6 +619,99 @@
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row class="border-bottom-c flex flex-col py-7 lg:flex-row">
|
||||
<Column sm={4} md={4} lg={4}
|
||||
><SectionTitle>{$t('settings.landing_page.custom_links.heading')}</SectionTitle>
|
||||
<Toggle bind:toggled={$landingPageSettings.customLinks.show} size="sm">
|
||||
<span slot="labelA" style="color: gray"
|
||||
>{$t('settings.landing_page.custom_links.hide_links')}</span
|
||||
>
|
||||
<span slot="labelB" style="color: gray"
|
||||
>{$t('settings.landing_page.custom_links.show_links')}</span
|
||||
>
|
||||
</Toggle>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
|
||||
<p class="mb-4 text-sm text-gray-600">
|
||||
{$t('settings.landing_page.custom_links.description')}
|
||||
</p>
|
||||
|
||||
{#each $landingPageSettings.customLinks.links as link (link.id)}
|
||||
<div class="mb-4 rounded-lg border border-gray-200 p-4">
|
||||
<div class="mb-3 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<TextField
|
||||
label={$t('settings.landing_page.custom_links.label')}
|
||||
placeholder={$t('settings.landing_page.custom_links.label_placeholder')}
|
||||
bind:value={link.label}
|
||||
className="mb-3"
|
||||
/>
|
||||
<TextField
|
||||
label={$t('settings.landing_page.custom_links.url')}
|
||||
placeholder={$t('settings.landing_page.custom_links.url_placeholder')}
|
||||
bind:value={link.url}
|
||||
className="mb-3"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" bind:checked={link.openInNewTab} class="mr-2" />
|
||||
<span class="text-sm">{$t('settings.landing_page.custom_links.new_tab')}</span>
|
||||
</label>
|
||||
<IconButton onClick={() => deleteCustomLink(link.id)}>
|
||||
<TrashCan size={20} class="fill-red-700" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#if creatingNewCustomLink}
|
||||
<div class="mb-4 rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<div class="mb-3 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<TextField
|
||||
label={$t('settings.landing_page.custom_links.label')}
|
||||
placeholder={$t('settings.landing_page.custom_links.label_placeholder')}
|
||||
bind:value={newCustomLink.label}
|
||||
className="mb-3"
|
||||
/>
|
||||
<TextField
|
||||
label={$t('settings.landing_page.custom_links.url')}
|
||||
placeholder={$t('settings.landing_page.custom_links.url_placeholder')}
|
||||
bind:value={newCustomLink.url}
|
||||
className="mb-3"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" bind:checked={newCustomLink.openInNewTab} class="mr-2" />
|
||||
<span class="text-sm">{$t('settings.landing_page.custom_links.new_tab')}</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label={$t('settings.landing_page.custom_links.save')}
|
||||
onClick={saveNewCustomLink}
|
||||
className="text-sm px-3 py-1"
|
||||
/>
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label={$t('settings.landing_page.custom_links.cancel')}
|
||||
onClick={cancelNewCustomLink}
|
||||
className="text-sm px-3 py-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label={$t('settings.landing_page.custom_links.add')}
|
||||
onClick={createNewCustomLink}
|
||||
className="mt-2"
|
||||
/>
|
||||
{/if}
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row class="flex flex-col py-7 lg:flex-row">
|
||||
<Column sm={4} md={4} lg={4}
|
||||
><SectionTitle>{$t('settings.landing_page.footer.heading')}</SectionTitle>
|
||||
@@ -640,7 +774,7 @@
|
||||
>
|
||||
<span>
|
||||
<IconButton onClick={handleSave} disabled={isSaving}>
|
||||
<Save size={40} class=" rounded-full bg-blue-700 p-1" />
|
||||
<Save size={32} class=" rounded-full bg-blue-700 p-1" />
|
||||
</IconButton>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface OrgLandingPageJson {
|
||||
type: string;
|
||||
show: boolean;
|
||||
};
|
||||
background?: {
|
||||
background: {
|
||||
image: string;
|
||||
show: boolean;
|
||||
};
|
||||
@@ -59,6 +59,15 @@ export interface OrgLandingPageJson {
|
||||
buttonLabel: string;
|
||||
show: boolean;
|
||||
};
|
||||
customLinks: {
|
||||
show: boolean;
|
||||
links: Array<{
|
||||
id: number;
|
||||
label: string;
|
||||
url: string;
|
||||
openInNewTab: boolean;
|
||||
}>;
|
||||
};
|
||||
footer: {
|
||||
facebook: string;
|
||||
instagram: string;
|
||||
@@ -145,6 +154,35 @@ export const landingPageSettings: Writable<OrgLandingPageJson> = writable({
|
||||
buttonLabel: 'Subscribe',
|
||||
show: true
|
||||
},
|
||||
customLinks: {
|
||||
show: true,
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
label: 'About Us',
|
||||
url: '#aboutus',
|
||||
openInNewTab: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: 'Courses',
|
||||
url: '#courses',
|
||||
openInNewTab: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: 'FAQ',
|
||||
url: '#faq',
|
||||
openInNewTab: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: 'Contact Us',
|
||||
url: '#contact',
|
||||
openInNewTab: false
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
facebook: '#',
|
||||
instagram: '#',
|
||||
|
||||
@@ -60,6 +60,35 @@
|
||||
"buttonLabel": "Subscribe",
|
||||
"show": true
|
||||
},
|
||||
"customLinks": {
|
||||
"show": true,
|
||||
"links": [
|
||||
{
|
||||
"id": 1,
|
||||
"label": "About Us",
|
||||
"url": "#about-us",
|
||||
"openInNewTab": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"label": "Courses",
|
||||
"url": "#courses",
|
||||
"openInNewTab": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"label": "FAQ",
|
||||
"url": "#faq",
|
||||
"openInNewTab": false
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"label": "Contact Us",
|
||||
"url": "#contact",
|
||||
"openInNewTab": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"footer": {
|
||||
"facebook": "#",
|
||||
"instagram": "#",
|
||||
|
||||
@@ -1286,6 +1286,20 @@
|
||||
"title": "Hintergrundbild",
|
||||
"show_background": "Hintergrund anzeigen",
|
||||
"hide_background": "Hintergrund ausblenden"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "Benutzerdefinierte Links",
|
||||
"hide_links": "Links verbergen",
|
||||
"show_links": "Links anzeigen",
|
||||
"description": "Fügen Sie benutzerdefinierte Navigationslinks hinzu, die in der Header -Navigationsleiste angezeigt werden.",
|
||||
"label": "Etikett",
|
||||
"url": "URL",
|
||||
"label_placeholder": "z. B. über uns",
|
||||
"url_placeholder": "z. B. #über- oder https://example.com",
|
||||
"new_tab": "Offen in neuer Registerkarte",
|
||||
"save": "Speichern",
|
||||
"cancel": "Stornieren",
|
||||
"add": "Neuen Link hinzufügen"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
@@ -226,6 +226,21 @@
|
||||
"twitter": "Twitter",
|
||||
"linkedin": "LinkedIn"
|
||||
},
|
||||
|
||||
"custom_links": {
|
||||
"heading": "Custom Links",
|
||||
"hide_links": "Hide Links",
|
||||
"show_links": "Show Links",
|
||||
"description": "Add custom navigation links that will appear in the header navigation bar.",
|
||||
"label": "Label",
|
||||
"url": "URL",
|
||||
"label_placeholder": "e.g., About Us",
|
||||
"url_placeholder": "e.g., #about-us or https://example.com",
|
||||
"new_tab": "Open in new tab",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"add": "Add New Link"
|
||||
},
|
||||
"save_changes": "Save Changes"
|
||||
},
|
||||
"integrations": {
|
||||
|
||||
@@ -1286,6 +1286,20 @@
|
||||
"title": "Imagen de fondo",
|
||||
"show_background": "Mostrar fondo",
|
||||
"hide_background": "Ocultar fondo"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "Enlaces personalizados",
|
||||
"hide_links": "Ocultar enlaces",
|
||||
"show_links": "Mostrar enlaces",
|
||||
"description": "Agregue enlaces de navegación personalizados que aparecerán en la barra de navegación del encabezado.",
|
||||
"label": "Etiqueta",
|
||||
"url": "Url",
|
||||
"label_placeholder": "por ejemplo, sobre nosotros",
|
||||
"url_placeholder": "por ejemplo, #About-US o https://example.com",
|
||||
"new_tab": "Abrir en una nueva pestaña",
|
||||
"save": "Ahorrar",
|
||||
"cancel": "Cancelar",
|
||||
"add": "Agregar nuevo enlace"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
@@ -1286,6 +1286,20 @@
|
||||
"title": "Image d'arrière-plan",
|
||||
"show_background": "Afficher l'arrière-plan",
|
||||
"hide_background": "Masquer l'arrière-plan"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "Liens personnalisés",
|
||||
"hide_links": "Masquer les liens",
|
||||
"show_links": "Montrer des liens",
|
||||
"description": "Ajoutez des liens de navigation personnalisés qui apparaîtront dans la barre de navigation d'en-tête.",
|
||||
"label": "Étiquette",
|
||||
"url": "URL",
|
||||
"label_placeholder": "par exemple, à propos de nous",
|
||||
"url_placeholder": "par exemple, # à peu près ou https://example.com",
|
||||
"new_tab": "Ouvert dans un nouvel onglet",
|
||||
"save": "Sauvegarder",
|
||||
"cancel": "Annuler",
|
||||
"add": "Ajouter un nouveau lien"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
@@ -1286,6 +1286,20 @@
|
||||
"title": "पृष्ठभूमि छवि",
|
||||
"show_background": "पृष्ठभूमि दिखाएँ",
|
||||
"hide_background": "पृष्ठभूमि छिपाएँ"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "कस्टम लिंक",
|
||||
"hide_links": "लिंक छिपाएं",
|
||||
"show_links": "लिंक दिखाएं",
|
||||
"description": "हेडर नेविगेशन बार में दिखाई देने वाले कस्टम नेविगेशन लिंक जोड़ें।",
|
||||
"label": "लेबल",
|
||||
"url": "यूआरएल",
|
||||
"label_placeholder": "जैसे, हमारे बारे में",
|
||||
"url_placeholder": "उदा।, #के बारे में या https://example.com",
|
||||
"new_tab": "वेब टेब में खोलें",
|
||||
"save": "बचाना",
|
||||
"cancel": "रद्द करना",
|
||||
"add": "नया लिंक जोड़ें"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
@@ -1402,6 +1402,20 @@
|
||||
"title": "Obraz tła",
|
||||
"show_background": "Pokaż tło",
|
||||
"hide_background": "Ukryj tło"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "Niestandardowe linki",
|
||||
"hide_links": "Ukryj linki",
|
||||
"show_links": "Pokaż linki",
|
||||
"description": "Dodaj niestandardowe linki nawigacyjne, które pojawią się na pasku nawigacji nagłówka.",
|
||||
"label": "Etykieta",
|
||||
"url": "URL",
|
||||
"label_placeholder": "np. O nas",
|
||||
"url_placeholder": "np. #About-US lub https://example.com",
|
||||
"new_tab": "Otwórz w nowej zakładce",
|
||||
"save": "Ratować",
|
||||
"cancel": "Anulować",
|
||||
"add": "Dodaj nowy link"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
@@ -1286,6 +1286,20 @@
|
||||
"title": "Imagem de fundo",
|
||||
"show_background": "Mostrar plano de fundo",
|
||||
"hide_background": "Ocultar plano de fundo"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "Links personalizados",
|
||||
"hide_links": "Ocultar links",
|
||||
"show_links": "Mostre links",
|
||||
"description": "Adicione links de navegação personalizados que aparecerão na barra de navegação do cabeçalho.",
|
||||
"label": "Rótulo",
|
||||
"url": "Url",
|
||||
"label_placeholder": "por exemplo, sobre nós",
|
||||
"url_placeholder": "por exemplo, #sobre-us ou https://example.com",
|
||||
"new_tab": "Abrir na nova guia",
|
||||
"save": "Salvar",
|
||||
"cancel": "Cancelar",
|
||||
"add": "Adicione um novo link"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
@@ -1286,6 +1286,20 @@
|
||||
"title": "Фоновое изображение",
|
||||
"show_background": "Показать фон",
|
||||
"hide_background": "Скрыть фон"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "Пользовательские ссылки",
|
||||
"hide_links": "Скрыть ссылки",
|
||||
"show_links": "Показать ссылки",
|
||||
"description": "Добавьте пользовательские навигационные ссылки, которые появятся в панели навигации заголовка.",
|
||||
"label": "Этикетка",
|
||||
"url": "URL",
|
||||
"label_placeholder": "например, о нас",
|
||||
"url_placeholder": "Например, #about-us или https://example.com",
|
||||
"new_tab": "Открыто в новой вкладке",
|
||||
"save": "Сохранять",
|
||||
"cancel": "Отмена",
|
||||
"add": "Добавить новую ссылку"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
@@ -1286,6 +1286,20 @@
|
||||
"title": "Hình nền",
|
||||
"show_background": "Hiển thị nền",
|
||||
"hide_background": "Ẩn nền"
|
||||
},
|
||||
"custom_links": {
|
||||
"heading": "Liên kết tùy chỉnh",
|
||||
"hide_links": "Ẩn liên kết",
|
||||
"show_links": "Hiển thị liên kết",
|
||||
"description": "Thêm các liên kết điều hướng tùy chỉnh sẽ xuất hiện trong thanh điều hướng tiêu đề.",
|
||||
"label": "Nhãn",
|
||||
"url": "URL",
|
||||
"label_placeholder": "ví dụ: về chúng tôi",
|
||||
"url_placeholder": "ví dụ: #abing-us hoặc https://example.com",
|
||||
"new_tab": "Mở trong tab mới",
|
||||
"save": "Cứu",
|
||||
"cancel": "Hủy bỏ",
|
||||
"add": "Thêm liên kết mới"
|
||||
}
|
||||
},
|
||||
"organization": {
|
||||
|
||||
Reference in New Issue
Block a user