From 0975f82c8b75bc55b020c5415a5481784c6e935e Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Sat, 13 Jun 2026 11:24:20 +0100 Subject: [PATCH] fix(widget): powered toggle persistence, copy buttons, version dates, showLessonsCount, color pickers, and loading spinner --- apps/api/src/services/widget.ts | 15 +++- apps/dashboard/src/hooks.server.ts | 1 + .../src/lib/features/ui/upgrade-modal.svelte | 7 +- .../components/widget-editor-header.svelte | 7 +- .../widget/pages/widget-editor.svelte | 2 + .../widget/panels/design-panel.svelte | 86 +++++++++++++------ .../features/widget/panels/embed-panel.svelte | 19 ++-- .../store/widget-editor.store.svelte.ts | 64 ++++++++------ .../custom/widget-layouts/card-grid.svelte | 2 +- .../src/custom/widget-layouts/carousel.svelte | 2 +- .../custom/widget-layouts/compact-list.svelte | 3 +- .../widget-layouts/course-widget.svelte | 14 ++- .../widget-layouts/primary-course.svelte | 2 +- .../ui/src/custom/widget-layouts/utils.ts | 3 +- 14 files changed, 153 insertions(+), 74 deletions(-) diff --git a/apps/api/src/services/widget.ts b/apps/api/src/services/widget.ts index ef6b3afe..7e405e68 100644 --- a/apps/api/src/services/widget.ts +++ b/apps/api/src/services/widget.ts @@ -23,7 +23,7 @@ import { type TUpdateWidget } from '@cio/utils/validation/widget'; -import { getOrganizationById } from '@cio/db/queries/organization'; +import { getActiveOrganizationPlan, getOrganizationById } from '@cio/db/queries/organization'; import { buildWidgetPayload, generateWidgetPublicKey, @@ -48,13 +48,14 @@ export async function listOrganizationWidgets(orgId: string) { export async function createOrganizationWidget(orgId: string, userId: string, data: TCreateWidget) { try { + const activePlan = await getActiveOrganizationPlan(orgId); const widget = await createWidget({ organizationId: orgId, name: data.name, layoutType: data.layoutType, selectionMode: data.selectionMode, publicKey: generateWidgetPublicKey(), - config: normalizeWidgetConfig(data.config ?? getDefaultWidgetConfig()), + config: normalizeWidgetConfig(data.config ?? getDefaultWidgetConfig(), activePlan?.planName ?? null), createdByUserId: userId, updatedByUserId: userId }); @@ -132,7 +133,10 @@ export async function getOrganizationWidgetDetail(orgId: string, widgetId: strin export async function updateOrganizationWidget(orgId: string, widgetId: string, userId: string, data: TUpdateWidget) { try { - const existingWidget = await getWidgetById(orgId, widgetId); + const [existingWidget, activePlan] = await Promise.all([ + getWidgetById(orgId, widgetId), + getActiveOrganizationPlan(orgId) + ]); if (!existingWidget) { throw new AppError('Widget not found', ErrorCodes.WIDGET_NOT_FOUND, 404); } @@ -141,7 +145,10 @@ export async function updateOrganizationWidget(orgId: string, widgetId: string, name: data.name ?? existingWidget.name, layoutType: data.layoutType ?? existingWidget.layoutType, selectionMode: data.selectionMode ?? existingWidget.selectionMode, - config: normalizeWidgetConfig((data.config ?? existingWidget.config) as Record), + config: normalizeWidgetConfig( + (data.config ?? existingWidget.config) as Record, + activePlan?.planName ?? null + ), hasUnpublishedChanges: true, updatedByUserId: userId }); diff --git a/apps/dashboard/src/hooks.server.ts b/apps/dashboard/src/hooks.server.ts index 5f00a013..d653c7d8 100644 --- a/apps/dashboard/src/hooks.server.ts +++ b/apps/dashboard/src/hooks.server.ts @@ -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) { diff --git a/apps/dashboard/src/lib/features/ui/upgrade-modal.svelte b/apps/dashboard/src/lib/features/ui/upgrade-modal.svelte index 7debd6ac..8f62d433 100644 --- a/apps/dashboard/src/lib/features/ui/upgrade-modal.svelte +++ b/apps/dashboard/src/lib/features/ui/upgrade-modal.svelte @@ -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() { diff --git a/apps/dashboard/src/lib/features/widget/components/widget-editor-header.svelte b/apps/dashboard/src/lib/features/widget/components/widget-editor-header.svelte index e27ff6bd..0505a121 100644 --- a/apps/dashboard/src/lib/features/widget/components/widget-editor-header.svelte +++ b/apps/dashboard/src/lib/features/widget/components/widget-editor-header.svelte @@ -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')} - diff --git a/apps/dashboard/src/lib/features/widget/pages/widget-editor.svelte b/apps/dashboard/src/lib/features/widget/pages/widget-editor.svelte index d79d20ca..78ce1b87 100644 --- a/apps/dashboard/src/lib/features/widget/pages/widget-editor.svelte +++ b/apps/dashboard/src/lib/features/widget/pages/widget-editor.svelte @@ -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()} diff --git a/apps/dashboard/src/lib/features/widget/panels/design-panel.svelte b/apps/dashboard/src/lib/features/widget/panels/design-panel.svelte index 84d5259c..ec5c98d0 100644 --- a/apps/dashboard/src/lib/features/widget/panels/design-panel.svelte +++ b/apps/dashboard/src/lib/features/widget/panels/design-panel.svelte @@ -45,33 +45,65 @@ - - - - + + {$t('widgets.form.primary_color')} +
+ + {draftConfig.colors.primaryColor} +
+ {#if errFor('config.colors.primaryColor')} + {errFor('config.colors.primaryColor')} + {/if} +
+ + {$t('widgets.form.background_color')} +
+ + {draftConfig.colors.backgroundColor} +
+ {#if errFor('config.colors.backgroundColor')} + {errFor('config.colors.backgroundColor')} + {/if} +
+ + {$t('widgets.form.text_color')} +
+ + {draftConfig.colors.textColor} +
+ {#if errFor('config.colors.textColor')} + {errFor('config.colors.textColor')} + {/if} +
+ + {$t('widgets.form.border_color')} +
+ + {draftConfig.colors.borderColor} +
+ {#if errFor('config.colors.borderColor')} + {errFor('config.colors.borderColor')} + {/if} +
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 @@
- +
@@ -36,9 +37,9 @@

{$t('widgets.embed.url_helper')}

- +
@@ -50,7 +51,15 @@
v{version.version}
-
{version.publishedAt}
+
+ {new Date(version.publishedAt).toLocaleString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: '2-digit' + })} +
diff --git a/packages/ui/src/custom/widget-layouts/carousel.svelte b/packages/ui/src/custom/widget-layouts/carousel.svelte index 0442faf8..7b57281e 100644 --- a/packages/ui/src/custom/widget-layouts/carousel.svelte +++ b/packages/ui/src/custom/widget-layouts/carousel.svelte @@ -91,7 +91,7 @@ {/if} -
{formatLessonsLine(course)}
+
{formatLessonsLine(course, payload.design.content.showLessonsCount)}
{/each} diff --git a/packages/ui/src/custom/widget-layouts/compact-list.svelte b/packages/ui/src/custom/widget-layouts/compact-list.svelte index 08ee0a40..e0e475c9 100644 --- a/packages/ui/src/custom/widget-layouts/compact-list.svelte +++ b/packages/ui/src/custom/widget-layouts/compact-list.svelte @@ -3,6 +3,7 @@ import Stars from './atoms/stars.svelte'; import TypeBadge from './atoms/type-badge.svelte'; import Thumb from './atoms/thumb.svelte'; + import { formatLessonsLine } from './utils'; let { payload }: { payload: TWidgetPayload } = $props(); @@ -25,7 +26,7 @@
{course.title} {#if payload.design.content.showLessonsCount} - {course.lessonCount} lessons + {formatLessonsLine(course, payload.design.content.showLessonsCount)} {/if}
diff --git a/packages/ui/src/custom/widget-layouts/course-widget.svelte b/packages/ui/src/custom/widget-layouts/course-widget.svelte index f66800b8..c6dafd20 100644 --- a/packages/ui/src/custom/widget-layouts/course-widget.svelte +++ b/packages/ui/src/custom/widget-layouts/course-widget.svelte @@ -22,15 +22,23 @@ if (!payload || !rootEl) return; ensureWidgetGoogleFontsLoaded(rootEl, payload.design); }); + + $effect(() => { + if (!rootEl) return; + const existing = rootEl.querySelector('[data-cio-widget-custom-css]'); + if (existing) existing.remove(); + if (!customCss) return; + const styleEl = document.createElement('style'); + styleEl.setAttribute('data-cio-widget-custom-css', ''); + styleEl.textContent = customCss; + rootEl.appendChild(styleEl); + }); {#if !payload}
No widget payload provided.
{:else}
- {#if customCss} - {customCss} - {/if} {#if payload.layoutType === 'card_grid'} {:else if payload.layoutType === 'tag_filter'} diff --git a/packages/ui/src/custom/widget-layouts/primary-course.svelte b/packages/ui/src/custom/widget-layouts/primary-course.svelte index e98586f2..dff0473b 100644 --- a/packages/ui/src/custom/widget-layouts/primary-course.svelte +++ b/packages/ui/src/custom/widget-layouts/primary-course.svelte @@ -31,7 +31,7 @@ {/if} · {/if} - {formatLessonsLine(featured)} + {formatLessonsLine(featured, payload.design.content.showLessonsCount)}
{#if payload.design.content.showCourseTypeBadge || featured.tags.length > 0}
diff --git a/packages/ui/src/custom/widget-layouts/utils.ts b/packages/ui/src/custom/widget-layouts/utils.ts index 4b0e09cf..35de1829 100644 --- a/packages/ui/src/custom/widget-layouts/utils.ts +++ b/packages/ui/src/custom/widget-layouts/utils.ts @@ -56,7 +56,8 @@ export function getCourseExcerpt(course: TWidgetPayloadCourse, maxLength = 160): return `${flat.slice(0, maxLength - 1).trim()}…`; } -export function formatLessonsLine(course: TWidgetPayloadCourse): string { +export function formatLessonsLine(course: TWidgetPayloadCourse, showLessonsCount?: boolean): string { + if (showLessonsCount === false) return ''; const parts: string[] = []; if (course.lessonCount) parts.push(`${course.lessonCount} lessons`); if (course.exerciseCount) parts.push(`${course.exerciseCount} exercises`);