2025-12-02 09:54:38 +01:00
|
|
|
<script lang="ts">
|
2026-04-12 08:58:41 +01:00
|
|
|
import { resolve } from '$app/paths';
|
|
|
|
|
import { page } from '$app/state';
|
|
|
|
|
import { goto } from '$app/navigation';
|
|
|
|
|
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
|
|
|
import { currentOrg, currentOrgPath, isFreePlan } from '$lib/utils/store/org';
|
|
|
|
|
import { openUpgradeModal } from '$lib/utils/functions/org';
|
2025-12-02 09:54:38 +01:00
|
|
|
import { t } from '$lib/utils/functions/translations';
|
|
|
|
|
import { Button } from '@cio/ui/base/button';
|
|
|
|
|
import * as Field from '@cio/ui/base/field';
|
2026-04-12 08:58:41 +01:00
|
|
|
import * as RadioGroup from '@cio/ui/base/radio-group';
|
|
|
|
|
import { UnsavedChanges } from '$features/ui';
|
|
|
|
|
import { orgApi } from '$features/org/api/org.svelte';
|
|
|
|
|
import {
|
|
|
|
|
createDefaultLandingPageSettings,
|
|
|
|
|
landingPageThemes,
|
|
|
|
|
normalizeLandingPageSettings
|
|
|
|
|
} from '$features/org/utils/landing-page';
|
|
|
|
|
import { landingPageSettings } from '../utils/store';
|
|
|
|
|
import { PremiumIcon } from '@cio/ui/custom/moving-icons';
|
2025-12-22 02:36:48 +01:00
|
|
|
import type { AccountOrg } from '$features/app/types';
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
let hasInitialized = $state(false);
|
|
|
|
|
let skipUnsavedPrompt = $state(false);
|
|
|
|
|
let { hasUnsavedChanges = $bindable(false) }: { hasUnsavedChanges?: boolean } = $props();
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
const themeCards = [
|
|
|
|
|
{
|
|
|
|
|
value: 'minimal',
|
|
|
|
|
preview: 'https://assets.cdn.clsrio.com/www/templates/minimal.png',
|
|
|
|
|
titleKey: 'settings.landing_page.theme.cards.minimal.title',
|
|
|
|
|
descriptionKey: 'settings.landing_page.theme.cards.minimal.description'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
value: 'bold',
|
|
|
|
|
preview: 'https://assets.cdn.clsrio.com/www/templates/bold.png',
|
|
|
|
|
titleKey: 'settings.landing_page.theme.cards.bold.title',
|
|
|
|
|
descriptionKey: 'settings.landing_page.theme.cards.bold.description'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
value: 'classic',
|
|
|
|
|
preview: 'https://assets.cdn.clsrio.com/www/templates/classic.png',
|
|
|
|
|
titleKey: 'settings.landing_page.theme.cards.classic.title',
|
|
|
|
|
descriptionKey: 'settings.landing_page.theme.cards.classic.description'
|
|
|
|
|
}
|
|
|
|
|
] as const;
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
function isPaidTheme(theme: string): boolean {
|
|
|
|
|
return theme !== 'minimal';
|
2025-12-02 09:54:38 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
function selectTheme(theme: (typeof landingPageThemes)[number]) {
|
|
|
|
|
if ($isFreePlan && isPaidTheme(theme)) {
|
|
|
|
|
openUpgradeModal();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$landingPageSettings.theme = theme;
|
|
|
|
|
hasUnsavedChanges = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isLandingPageTheme(value: string | null): value is (typeof landingPageThemes)[number] {
|
|
|
|
|
return value === 'minimal' || value === 'bold' || value === 'classic';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getEditContentUrl() {
|
|
|
|
|
const query = new SvelteURLSearchParams(page.url.searchParams);
|
|
|
|
|
query.set('theme', $landingPageSettings.theme);
|
|
|
|
|
return `${$currentOrgPath}/settings/landingpage/edit?${query.toString()}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (hasInitialized || !$currentOrg.id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizedLandingPage = normalizeLandingPageSettings($currentOrg.landingpage);
|
|
|
|
|
const orgDefaultLandingPageSettings = createDefaultLandingPageSettings();
|
|
|
|
|
const queryTheme = page.url.searchParams.get('theme');
|
|
|
|
|
$landingPageSettings = {
|
|
|
|
|
...orgDefaultLandingPageSettings,
|
|
|
|
|
...normalizedLandingPage
|
2025-12-02 09:54:38 +01:00
|
|
|
};
|
2026-04-12 08:58:41 +01:00
|
|
|
if (isLandingPageTheme(queryTheme)) {
|
|
|
|
|
$landingPageSettings.theme = queryTheme;
|
|
|
|
|
hasUnsavedChanges = queryTheme !== normalizedLandingPage.theme;
|
|
|
|
|
} else {
|
|
|
|
|
hasUnsavedChanges = false;
|
2025-12-02 09:54:38 +01:00
|
|
|
}
|
2026-04-12 08:58:41 +01:00
|
|
|
hasInitialized = true;
|
|
|
|
|
});
|
2025-12-02 09:54:38 +01:00
|
|
|
|
|
|
|
|
export async function handleSave() {
|
2026-04-12 08:58:41 +01:00
|
|
|
const updatedLandingPage = normalizeLandingPageSettings($landingPageSettings as AccountOrg['landingpage']);
|
2025-12-02 09:54:38 +01:00
|
|
|
|
|
|
|
|
await orgApi.update($currentOrg.id, {
|
2026-04-12 08:58:41 +01:00
|
|
|
landingpage: updatedLandingPage as AccountOrg['landingpage']
|
2025-12-02 09:54:38 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (orgApi.success) {
|
2026-04-12 08:58:41 +01:00
|
|
|
$landingPageSettings = updatedLandingPage;
|
|
|
|
|
$currentOrg.landingpage = updatedLandingPage as AccountOrg['landingpage'];
|
2025-12-02 09:54:38 +01:00
|
|
|
hasUnsavedChanges = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
<UnsavedChanges bind:hasUnsavedChanges skipPrompt={skipUnsavedPrompt} />
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
<Field.Group class="w-full px-2">
|
2025-12-02 09:54:38 +01:00
|
|
|
<Field.Set>
|
2026-04-12 08:58:41 +01:00
|
|
|
<div class="flex items-start justify-between gap-4">
|
|
|
|
|
<div>
|
|
|
|
|
<Field.Legend>{$t('settings.landing_page.theme.heading')}</Field.Legend>
|
|
|
|
|
<Field.Description>{$t('settings.landing_page.theme.description')}</Field.Description>
|
|
|
|
|
</div>
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onclick={async () => {
|
|
|
|
|
skipUnsavedPrompt = true;
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
try {
|
|
|
|
|
await goto(resolve(getEditContentUrl(), {}));
|
|
|
|
|
} finally {
|
|
|
|
|
skipUnsavedPrompt = false;
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{$t('settings.landing_page.content.edit')}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2026-04-12 08:58:41 +01:00
|
|
|
<Field.Field>
|
|
|
|
|
<RadioGroup.Root
|
|
|
|
|
bind:value={$landingPageSettings.theme}
|
|
|
|
|
class="grid gap-4 md:grid-cols-3"
|
|
|
|
|
onValueChange={() => {
|
|
|
|
|
hasUnsavedChanges = true;
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{#each themeCards as themeCard (themeCard.value)}
|
|
|
|
|
{@const isLocked = $isFreePlan && isPaidTheme(themeCard.value)}
|
|
|
|
|
<RadioGroup.Item value={themeCard.value} id={themeCard.value} class="sr-only" />
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onclick={() => selectTheme(themeCard.value)}
|
|
|
|
|
aria-pressed={$landingPageSettings.theme === themeCard.value}
|
|
|
|
|
class={`group relative cursor-pointer rounded-xl border transition ${
|
|
|
|
|
isLocked
|
|
|
|
|
? 'border-border opacity-75 hover:border-blue-500/60'
|
|
|
|
|
: $landingPageSettings.theme === themeCard.value
|
|
|
|
|
? 'border-primary ring-primary/20 ring-2'
|
|
|
|
|
: 'border-border hover:border-primary/60'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{#if isLocked}
|
|
|
|
|
<div
|
|
|
|
|
class="absolute top-2 right-2 z-10 flex items-center gap-1 rounded-full bg-blue-600 px-2 py-0.5 text-xs font-medium text-white shadow-sm"
|
|
|
|
|
>
|
|
|
|
|
<PremiumIcon size={14} color="white" />
|
|
|
|
|
Upgrade
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
<div class="bg-background mb-4 overflow-hidden rounded-xl">
|
|
|
|
|
<img src={themeCard.preview} alt={$t(themeCard.titleKey)} class="h-50 w-full object-cover object-top" />
|
2025-12-02 09:54:38 +01:00
|
|
|
</div>
|
2026-04-12 08:58:41 +01:00
|
|
|
<div class="space-y-1 px-2 pb-2">
|
|
|
|
|
<p class="text-base font-semibold">{$t(themeCard.titleKey)}</p>
|
|
|
|
|
<p class="ui:text-muted-foreground h-[40px] text-sm">{$t(themeCard.descriptionKey)}</p>
|
2025-12-02 09:54:38 +01:00
|
|
|
</div>
|
2026-04-12 08:58:41 +01:00
|
|
|
</button>
|
|
|
|
|
{/each}
|
|
|
|
|
</RadioGroup.Root>
|
2025-12-02 09:54:38 +01:00
|
|
|
</Field.Field>
|
|
|
|
|
</Field.Set>
|
|
|
|
|
</Field.Group>
|