fix(widget): powered toggle persistence, copy buttons, version dates, showLessonsCount, color pickers, and loading spinner
This commit is contained in:
@@ -43,6 +43,7 @@ export const handle: Handle = async (args) => {
|
||||
|
||||
const cookieString = event.cookies.getAll().map((c) => `${c.name}=${c.value}`);
|
||||
console.log('event.cookies', cookieString);
|
||||
console.log('page path', event.url.pathname);
|
||||
const sessionData = await getSessionData(event.cookies);
|
||||
|
||||
if (sessionData) {
|
||||
|
||||
@@ -49,14 +49,15 @@
|
||||
checkoutURL.searchParams.set('products', isYearlyPlan ? plan.CTA.PRODUCT_ID_YEARLY : plan.CTA.PRODUCT_ID);
|
||||
checkoutURL.searchParams.set('orgId', $currentOrg.id);
|
||||
|
||||
window.location.href = checkoutURL.toString();
|
||||
requestAnimationFrame(() => {
|
||||
window.location.href = checkoutURL.toString();
|
||||
});
|
||||
} catch (error) {
|
||||
isLoadingPlan = null;
|
||||
console.error('Error subscribing', error);
|
||||
|
||||
snackbar.error('snackbar.upgrade.failed');
|
||||
}
|
||||
|
||||
isLoadingPlan = null;
|
||||
}
|
||||
|
||||
function onUpgrade() {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
isDirty: boolean;
|
||||
layoutValidationError: string | null;
|
||||
editingName: boolean;
|
||||
saving: boolean;
|
||||
publishing: boolean;
|
||||
onSave: () => void;
|
||||
onPublish: () => void;
|
||||
onDiscard: () => void;
|
||||
@@ -29,6 +31,8 @@
|
||||
isDirty,
|
||||
layoutValidationError,
|
||||
editingName,
|
||||
saving,
|
||||
publishing,
|
||||
onSave,
|
||||
onPublish,
|
||||
onDiscard,
|
||||
@@ -103,11 +107,12 @@
|
||||
size="sm"
|
||||
type="button"
|
||||
disabled={!isDirty || Boolean(layoutValidationError)}
|
||||
loading={saving}
|
||||
onclick={onSave}
|
||||
>
|
||||
{$t('widgets.editor.save_changes')}
|
||||
</Button>
|
||||
<Button size="sm" type="button" disabled={Boolean(layoutValidationError)} onclick={onPublish}>
|
||||
<Button size="sm" type="button" disabled={Boolean(layoutValidationError)} loading={publishing} onclick={onPublish}>
|
||||
{$t('widgets.actions.publish')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
isDirty={store.isDirty}
|
||||
layoutValidationError={store.layoutValidationError}
|
||||
editingName={store.editingName}
|
||||
saving={store.saving}
|
||||
publishing={store.publishing}
|
||||
onSave={() => store.saveWidget()}
|
||||
onPublish={() => store.publishWidget()}
|
||||
onDiscard={() => store.resetDraft()}
|
||||
|
||||
@@ -45,33 +45,65 @@
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</Field.Field>
|
||||
<InputField
|
||||
label={$t('widgets.form.primary_color')}
|
||||
bind:value={draftConfig.colors.primaryColor}
|
||||
maxLength={64}
|
||||
errorMessage={errFor('config.colors.primaryColor')}
|
||||
/>
|
||||
<InputField
|
||||
label={$t('widgets.form.background_color')}
|
||||
bind:value={draftConfig.colors.backgroundColor}
|
||||
isDisabled={!canUseCustomColors}
|
||||
maxLength={64}
|
||||
errorMessage={errFor('config.colors.backgroundColor')}
|
||||
/>
|
||||
<InputField
|
||||
label={$t('widgets.form.text_color')}
|
||||
bind:value={draftConfig.colors.textColor}
|
||||
isDisabled={!canUseCustomColors}
|
||||
maxLength={64}
|
||||
errorMessage={errFor('config.colors.textColor')}
|
||||
/>
|
||||
<InputField
|
||||
label={$t('widgets.form.border_color')}
|
||||
bind:value={draftConfig.colors.borderColor}
|
||||
isDisabled={!canUseCustomColors}
|
||||
maxLength={64}
|
||||
errorMessage={errFor('config.colors.borderColor')}
|
||||
/>
|
||||
<Field.Field>
|
||||
<Field.Label>{$t('widgets.form.primary_color')}</Field.Label>
|
||||
<div class="ui:flex ui:items-center ui:gap-2">
|
||||
<input
|
||||
type="color"
|
||||
bind:value={draftConfig.colors.primaryColor}
|
||||
class="ui:h-9 ui:w-9 ui:cursor-pointer ui:rounded ui:border ui:bg-transparent ui:p-0.5"
|
||||
/>
|
||||
<span class="ui:text-muted-foreground ui:font-mono ui:text-sm">{draftConfig.colors.primaryColor}</span>
|
||||
</div>
|
||||
{#if errFor('config.colors.primaryColor')}
|
||||
<Field.Error>{errFor('config.colors.primaryColor')}</Field.Error>
|
||||
{/if}
|
||||
</Field.Field>
|
||||
<Field.Field>
|
||||
<Field.Label>{$t('widgets.form.background_color')}</Field.Label>
|
||||
<div class="ui:flex ui:items-center ui:gap-2">
|
||||
<input
|
||||
type="color"
|
||||
bind:value={draftConfig.colors.backgroundColor}
|
||||
disabled={!canUseCustomColors}
|
||||
class="ui:h-9 ui:w-9 ui:cursor-pointer ui:rounded ui:border ui:bg-transparent ui:p-0.5 disabled:ui:cursor-not-allowed disabled:ui:opacity-50"
|
||||
/>
|
||||
<span class="ui:text-muted-foreground ui:font-mono ui:text-sm">{draftConfig.colors.backgroundColor}</span>
|
||||
</div>
|
||||
{#if errFor('config.colors.backgroundColor')}
|
||||
<Field.Error>{errFor('config.colors.backgroundColor')}</Field.Error>
|
||||
{/if}
|
||||
</Field.Field>
|
||||
<Field.Field>
|
||||
<Field.Label>{$t('widgets.form.text_color')}</Field.Label>
|
||||
<div class="ui:flex ui:items-center ui:gap-2">
|
||||
<input
|
||||
type="color"
|
||||
bind:value={draftConfig.colors.textColor}
|
||||
disabled={!canUseCustomColors}
|
||||
class="ui:h-9 ui:w-9 ui:cursor-pointer ui:rounded ui:border ui:bg-transparent ui:p-0.5 disabled:ui:cursor-not-allowed disabled:ui:opacity-50"
|
||||
/>
|
||||
<span class="ui:text-muted-foreground ui:font-mono ui:text-sm">{draftConfig.colors.textColor}</span>
|
||||
</div>
|
||||
{#if errFor('config.colors.textColor')}
|
||||
<Field.Error>{errFor('config.colors.textColor')}</Field.Error>
|
||||
{/if}
|
||||
</Field.Field>
|
||||
<Field.Field>
|
||||
<Field.Label>{$t('widgets.form.border_color')}</Field.Label>
|
||||
<div class="ui:flex ui:items-center ui:gap-2">
|
||||
<input
|
||||
type="color"
|
||||
bind:value={draftConfig.colors.borderColor}
|
||||
disabled={!canUseCustomColors}
|
||||
class="ui:h-9 ui:w-9 ui:cursor-pointer ui:rounded ui:border ui:bg-transparent ui:p-0.5 disabled:ui:cursor-not-allowed disabled:ui:opacity-50"
|
||||
/>
|
||||
<span class="ui:text-muted-foreground ui:font-mono ui:text-sm">{draftConfig.colors.borderColor}</span>
|
||||
</div>
|
||||
{#if errFor('config.colors.borderColor')}
|
||||
<Field.Error>{errFor('config.colors.borderColor')}</Field.Error>
|
||||
{/if}
|
||||
</Field.Field>
|
||||
<InputField
|
||||
type="number"
|
||||
label={$t('widgets.form.border_radius')}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import { CopyButton } from '@cio/ui/base/copy-button';
|
||||
import * as Tabs from '@cio/ui/base/tabs';
|
||||
import { TextareaField } from '@cio/ui/custom/textarea-field';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
@@ -26,9 +27,9 @@
|
||||
<Tabs.Content value="html" class="mt-4 space-y-3">
|
||||
<TextareaField label={$t('widgets.form.embed_code')} value={detail.widget.embedCode} rows={6} readonly />
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button variant="outline" onclick={() => navigator.clipboard.writeText(detail.widget.embedCode)}>
|
||||
<CopyButton text={detail.widget.embedCode} variant="outline">
|
||||
{$t('widgets.actions.copy_embed')}
|
||||
</Button>
|
||||
</CopyButton>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
@@ -36,9 +37,9 @@
|
||||
<TextareaField label={$t('widgets.embed.url_label')} value={detail.widget.hostedEmbedUrl} rows={2} readonly />
|
||||
<p class="ui:text-muted-foreground text-xs">{$t('widgets.embed.url_helper')}</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button variant="outline" onclick={() => navigator.clipboard.writeText(detail.widget.hostedEmbedUrl)}>
|
||||
<CopyButton text={detail.widget.hostedEmbedUrl} variant="outline">
|
||||
{$t('widgets.actions.copy_url')}
|
||||
</Button>
|
||||
</CopyButton>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
@@ -50,7 +51,15 @@
|
||||
<div class="ui:border-border ui:bg-card/50 flex items-center justify-between rounded-2xl border p-3">
|
||||
<div>
|
||||
<div class="font-medium">v{version.version}</div>
|
||||
<div class="ui:text-muted-foreground text-xs">{version.publishedAt}</div>
|
||||
<div class="ui:text-muted-foreground text-xs">
|
||||
{new Date(version.publishedAt).toLocaleString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onclick={() => onRollback(version.id)}>
|
||||
{$t('widgets.actions.restore')}
|
||||
|
||||
@@ -21,6 +21,8 @@ class WidgetEditorStore {
|
||||
selectedCourseIds = $state<string[]>([]);
|
||||
|
||||
// ── UI state ───────────────────────────────────────────────────────────────
|
||||
saving = $state(false);
|
||||
publishing = $state(false);
|
||||
activePanel = $state<PanelId>('select-courses');
|
||||
editingName = $state(false);
|
||||
courseSearch = $state('');
|
||||
@@ -180,43 +182,53 @@ class WidgetEditorStore {
|
||||
async saveWidget(silent = false): Promise<boolean> {
|
||||
if (!this.detail) return false;
|
||||
|
||||
this.saving = true;
|
||||
widgetApi.resetErrors();
|
||||
|
||||
const updatedWidget = await widgetApi.updateWidget(
|
||||
this.detail.widget.id,
|
||||
{
|
||||
name: this.draftName,
|
||||
layoutType: this.draftLayoutType,
|
||||
selectionMode: this.draftSelectionMode,
|
||||
config: this.draftConfig,
|
||||
selectedCourseIds: this.selectedCourseIds
|
||||
},
|
||||
silent
|
||||
);
|
||||
try {
|
||||
const updatedWidget = await widgetApi.updateWidget(
|
||||
this.detail.widget.id,
|
||||
{
|
||||
name: this.draftName,
|
||||
layoutType: this.draftLayoutType,
|
||||
selectionMode: this.draftSelectionMode,
|
||||
config: this.draftConfig,
|
||||
selectedCourseIds: this.selectedCourseIds
|
||||
},
|
||||
silent
|
||||
);
|
||||
|
||||
if (!updatedWidget) return false;
|
||||
if (!updatedWidget) return false;
|
||||
|
||||
await widgetApi.getWidget(this.detail.widget.id);
|
||||
if (widgetApi.widgetDetail) {
|
||||
this.applyDetail(widgetApi.widgetDetail);
|
||||
await widgetApi.getWidget(this.detail.widget.id);
|
||||
if (widgetApi.widgetDetail) {
|
||||
this.applyDetail(widgetApi.widgetDetail);
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async publishWidget() {
|
||||
const saved = await this.saveWidget(true);
|
||||
if (!saved || !this.detail) return;
|
||||
this.publishing = true;
|
||||
try {
|
||||
const saved = await this.saveWidget(true);
|
||||
if (!saved || !this.detail) return;
|
||||
|
||||
const published = await widgetApi.publishWidget(this.detail.widget.id);
|
||||
if (!published) return;
|
||||
const published = await widgetApi.publishWidget(this.detail.widget.id);
|
||||
if (!published) return;
|
||||
|
||||
await widgetApi.getWidget(this.detail.widget.id);
|
||||
if (widgetApi.widgetDetail) {
|
||||
this.applyDetail(widgetApi.widgetDetail);
|
||||
await widgetApi.getWidget(this.detail.widget.id);
|
||||
if (widgetApi.widgetDetail) {
|
||||
this.applyDetail(widgetApi.widgetDetail);
|
||||
}
|
||||
|
||||
this.activePanel = 'embed';
|
||||
} finally {
|
||||
this.publishing = false;
|
||||
}
|
||||
|
||||
this.activePanel = 'embed';
|
||||
}
|
||||
|
||||
async rollback(versionId: string) {
|
||||
|
||||
Reference in New Issue
Block a user