From e3a1b7b3481cdf107eaf2a5fe385fe197ccf2f16 Mon Sep 17 00:00:00 2001 From: Rajat Saxena Date: Mon, 11 Aug 2025 00:42:49 +0530 Subject: [PATCH 1/3] Embed and Marquee page blocks and icons for grid blocks --- .../components/admin/page-editor/index.tsx | 2 +- apps/web/graphql/pages/logic.ts | 148 ++++---- apps/web/ui-config/widgets.tsx | 13 +- .../src/admin-widget-panel.tsx | 63 +++- .../src/components/ui/alert.tsx | 62 ++++ packages/components-library/src/index.ts | 8 +- .../src/page-builder-slider.tsx | 31 +- .../banner/admin-widget/custom-settings.tsx | 210 ++++++----- .../src/blocks/content/admin-widget.tsx | 14 +- .../src/blocks/email-form/admin-widget.tsx | 16 +- .../src/blocks/embed/admin-widget.tsx | 224 ++++++++++++ .../page-blocks/src/blocks/embed/defaults.ts | 4 + .../page-blocks/src/blocks/embed/index.ts | 10 + .../page-blocks/src/blocks/embed/metadata.ts | 10 + .../page-blocks/src/blocks/embed/settings.ts | 9 + .../page-blocks/src/blocks/embed/widget.tsx | 104 ++++++ .../src/blocks/faq/admin-widget/index.tsx | 16 +- .../blocks/faq/admin-widget/item-editor.tsx | 63 ++-- .../src/blocks/featured/admin-widget.tsx | 16 +- .../src/blocks/footer/admin-widget/index.tsx | 14 +- .../src/blocks/grid/admin-widget/index.tsx | 84 ++++- .../blocks/grid/admin-widget/item-editor.tsx | 167 +++++---- .../blocks/grid/admin-widget/svg-editor.tsx | 214 ++++++++++++ .../grid/admin-widget/svg-style-editor.tsx | 172 +++++++++ .../page-blocks/src/blocks/grid/helpers.ts | 35 ++ .../page-blocks/src/blocks/grid/settings.ts | 15 + .../src/blocks/grid/widget/index.tsx | 4 + .../src/blocks/grid/widget/item.tsx | 38 +- .../src/blocks/header/admin-widget/index.tsx | 16 +- .../src/blocks/header/widget/index.tsx | 2 +- .../src/blocks/hero/admin-widget.tsx | 18 +- packages/page-blocks/src/blocks/index.tsx | 2 + .../src/blocks/marquee/admin-widget/index.tsx | 330 ++++++++++++++++++ .../marquee/admin-widget/item-editor.tsx | 108 ++++++ .../src/blocks/marquee/defaults.ts | 10 + .../page-blocks/src/blocks/marquee/index.ts | 10 + .../src/blocks/marquee/metadata.ts | 10 + .../src/blocks/marquee/settings.ts | 37 ++ .../src/blocks/marquee/widget/index.tsx | 123 +++++++ .../src/blocks/marquee/widget/marquee.tsx | 105 ++++++ .../src/blocks/media/admin-widget.tsx | 14 +- .../src/blocks/pricing/admin-widget/index.tsx | 14 +- .../pricing/admin-widget/item-editor.tsx | 168 ++++----- .../src/blocks/rich-text/admin-widget.tsx | 14 +- 44 files changed, 2282 insertions(+), 465 deletions(-) create mode 100644 packages/components-library/src/components/ui/alert.tsx create mode 100644 packages/page-blocks/src/blocks/embed/admin-widget.tsx create mode 100644 packages/page-blocks/src/blocks/embed/defaults.ts create mode 100644 packages/page-blocks/src/blocks/embed/index.ts create mode 100644 packages/page-blocks/src/blocks/embed/metadata.ts create mode 100644 packages/page-blocks/src/blocks/embed/settings.ts create mode 100644 packages/page-blocks/src/blocks/embed/widget.tsx create mode 100644 packages/page-blocks/src/blocks/grid/admin-widget/svg-editor.tsx create mode 100644 packages/page-blocks/src/blocks/grid/admin-widget/svg-style-editor.tsx create mode 100644 packages/page-blocks/src/blocks/grid/helpers.ts create mode 100644 packages/page-blocks/src/blocks/marquee/admin-widget/index.tsx create mode 100644 packages/page-blocks/src/blocks/marquee/admin-widget/item-editor.tsx create mode 100644 packages/page-blocks/src/blocks/marquee/defaults.ts create mode 100644 packages/page-blocks/src/blocks/marquee/index.ts create mode 100644 packages/page-blocks/src/blocks/marquee/metadata.ts create mode 100644 packages/page-blocks/src/blocks/marquee/settings.ts create mode 100644 packages/page-blocks/src/blocks/marquee/widget/index.tsx create mode 100644 packages/page-blocks/src/blocks/marquee/widget/marquee.tsx diff --git a/apps/web/components/admin/page-editor/index.tsx b/apps/web/components/admin/page-editor/index.tsx index ba9e47f1a..c508f8034 100644 --- a/apps/web/components/admin/page-editor/index.tsx +++ b/apps/web/components/admin/page-editor/index.tsx @@ -829,7 +829,7 @@ export default function PageEditor({ leftPaneContent === "widgets" ? EDIT_PAGE_ADD_WIDGET_TITLE : leftPaneContent === "editor" - ? "Edit Widget" + ? "Edit Block" : leftPaneContent === "fonts" ? "Fonts" : leftPaneContent === "theme" diff --git a/apps/web/graphql/pages/logic.ts b/apps/web/graphql/pages/logic.ts index e601aa668..2f31225a3 100644 --- a/apps/web/graphql/pages/logic.ts +++ b/apps/web/graphql/pages/logic.ts @@ -265,91 +265,91 @@ export const getPages = async ( }; export const initMandatoryPages = async (domain: Domain, user: User) => { - await PageModel.insertMany([ + await PageModel.bulkWrite([ { - domain: domain._id, - pageId: defaultPages[0], - type: site, - creatorId: user.userId, - name: pageNames.home, - entityId: domain.name, - layout: [ - { - name: "header", - deleteable: false, - shared: true, - }, - ...homePageTemplate, - { - name: "footer", - deleteable: false, - shared: true, + updateOne: { + filter: { domain: domain._id, pageId: defaultPages[0] }, + update: { + $setOnInsert: { + domain: domain._id, + pageId: defaultPages[0], + type: site, + creatorId: user.userId, + name: pageNames.home, + entityId: domain.name, + layout: [ + { name: "header", deleteable: false, shared: true }, + ...homePageTemplate, + { name: "footer", deleteable: false, shared: true }, + ], + draftLayout: [], + }, }, - ], - draftLayout: [], + upsert: true, + }, }, { - domain: domain._id, - pageId: defaultPages[2], - type: site, - creatorId: user.userId, - name: pageNames.privacy, - entityId: domain.name, - layout: [ - { - name: "header", - deleteable: false, - shared: true, - }, - { - name: "footer", - deleteable: false, - shared: true, + updateOne: { + filter: { domain: domain._id, pageId: defaultPages[2] }, + update: { + $setOnInsert: { + domain: domain._id, + pageId: defaultPages[2], + type: site, + creatorId: user.userId, + name: pageNames.privacy, + entityId: domain.name, + layout: [ + { name: "header", deleteable: false, shared: true }, + { name: "footer", deleteable: false, shared: true }, + ], + draftLayout: [], + }, }, - ], - draftLayout: [], + upsert: true, + }, }, { - domain: domain._id, - pageId: defaultPages[1], - type: site, - creatorId: user.userId, - name: pageNames.terms, - entityId: domain.name, - layout: [ - { - name: "header", - deleteable: false, - shared: true, - }, - { - name: "footer", - deleteable: false, - shared: true, + updateOne: { + filter: { domain: domain._id, pageId: defaultPages[1] }, + update: { + $setOnInsert: { + domain: domain._id, + pageId: defaultPages[1], + type: site, + creatorId: user.userId, + name: pageNames.terms, + entityId: domain.name, + layout: [ + { name: "header", deleteable: false, shared: true }, + { name: "footer", deleteable: false, shared: true }, + ], + draftLayout: [], + }, }, - ], - draftLayout: [], + upsert: true, + }, }, { - domain: domain._id, - pageId: defaultPages[3], - type: blogPage, - creatorId: user.userId, - name: pageNames.blog, - entityId: domain.name, - layout: [ - { - name: "header", - deleteable: false, - shared: true, - }, - { - name: "footer", - deleteable: false, - shared: true, + updateOne: { + filter: { domain: domain._id, pageId: defaultPages[3] }, + update: { + $setOnInsert: { + domain: domain._id, + pageId: defaultPages[3], + type: blogPage, + creatorId: user.userId, + name: pageNames.blog, + entityId: domain.name, + layout: [ + { name: "header", deleteable: false, shared: true }, + { name: "footer", deleteable: false, shared: true }, + ], + draftLayout: [], + }, }, - ], - draftLayout: [], + upsert: true, + }, }, ]); }; diff --git a/apps/web/ui-config/widgets.tsx b/apps/web/ui-config/widgets.tsx index 3e54d2125..535cfcf2d 100644 --- a/apps/web/ui-config/widgets.tsx +++ b/apps/web/ui-config/widgets.tsx @@ -12,12 +12,14 @@ import { FAQ, Pricing, Media, + Marquee, + Embed, } from "@courselit/page-blocks"; function loadWidgets(): Record { const widgets: Record = {}; - // Add common widgets to CourseLit + // Adding page blocks to CourseLit widgets[RichText.metadata.name] = RichText; widgets[Featured.metadata.name] = Featured; widgets[Banner.metadata.name] = Banner; @@ -27,14 +29,11 @@ function loadWidgets(): Record { widgets[FAQ.metadata.name] = FAQ; widgets[Pricing.metadata.name] = Pricing; widgets[Media.metadata.name] = Media; + widgets[Marquee.metadata.name] = Marquee; + widgets[Embed.metadata.name] = Embed; + widgets[EmailForm.metadata.name] = EmailForm; widgets[Footer.metadata.name] = Object.assign({}, Footer, { shared: true }); widgets[Header.metadata.name] = Object.assign({}, Header, { shared: true }); - widgets[EmailForm.metadata.name] = Object.assign({}, EmailForm, { - shared: true, - }); - - // Additional widgets are added here - // widgets[buttondown.metadata.name] = buttondown; return widgets; } diff --git a/packages/components-library/src/admin-widget-panel.tsx b/packages/components-library/src/admin-widget-panel.tsx index 21ec4f2f3..963ededdd 100644 --- a/packages/components-library/src/admin-widget-panel.tsx +++ b/packages/components-library/src/admin-widget-panel.tsx @@ -1,21 +1,74 @@ import * as React from "react"; import Section from "./section"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "./components/ui/accordion"; + +interface AdminWidgetPanelContainerProps { + children: React.ReactNode; + className?: string; + type?: "single" | "multiple"; + defaultValue?: string | string[]; +} + +export function AdminWidgetPanelContainer({ + children, + className = "", + type = "multiple", + defaultValue, +}: AdminWidgetPanelContainerProps) { + return ( +
+ {type === "single" ? ( + + {children} + + ) : ( + + {children} + + )} +
+ ); +} interface AdminWidgetPanelProps { title?: string; children: React.ReactNode; className?: string; + value: string; + defaultExpanded?: boolean; } -export default function AdminWidgetPanel({ +export function AdminWidgetPanel({ title, children, className = "", + value, }: AdminWidgetPanelProps) { + if (!title) { + // If no title, render as non-collapsible section + return
{children}
; + } + return ( -
- {title &&

{title}

} - {children} -
+ + + {title} + + + {children} + + ); } diff --git a/packages/components-library/src/components/ui/alert.tsx b/packages/components-library/src/components/ui/alert.tsx new file mode 100644 index 000000000..f9fee9e55 --- /dev/null +++ b/packages/components-library/src/components/ui/alert.tsx @@ -0,0 +1,62 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/packages/components-library/src/index.ts b/packages/components-library/src/index.ts index 2a09825e6..2c37c6571 100644 --- a/packages/components-library/src/index.ts +++ b/packages/components-library/src/index.ts @@ -11,7 +11,6 @@ import TextEditor, { emptyDoc as TextEditorEmptyDoc } from "./text-editor"; import TextRenderer from "./text-renderer"; import LessonIcon from "./lesson-icon"; import ColorSelector from "./color-selector"; -import AdminWidgetPanel from "./admin-widget-panel"; import Button from "./button"; import IconButton from "./icon-button"; import Form from "./form"; @@ -41,14 +40,17 @@ import Tooltip from "./tooltip"; import DragAndDrop from "./drag-and-drop"; export { Button as Button2 } from "./components/ui/button"; -export * from "./menu"; export * from "./components/ui/avatar"; export * from "./components/ui/accordion"; export * from "./components/ui/slider"; export * from "./components/ui/card"; export * from "./components/ui/badge"; export * from "./components/ui/skeleton"; +export * from "./components/ui/textarea"; +export * from "./components/ui/alert"; +export * from "./admin-widget-panel"; +export * from "./menu"; export * from "./toast2"; export * from "./paginated-table"; import getSymbolFromCurrency from "currency-symbol-map"; @@ -58,6 +60,7 @@ export * from "./video-with-preview"; export * from "./image"; export * from "./vertical-padding-selector"; export * from "./max-width-selector"; +export * from "./lib/utils"; export { PriceTag, @@ -72,7 +75,6 @@ export { TextRenderer, LessonIcon, ColorSelector, - AdminWidgetPanel, Button, IconButton, Form, diff --git a/packages/components-library/src/page-builder-slider.tsx b/packages/components-library/src/page-builder-slider.tsx index a1083f7d6..a806e7a1e 100644 --- a/packages/components-library/src/page-builder-slider.tsx +++ b/packages/components-library/src/page-builder-slider.tsx @@ -1,5 +1,6 @@ import { Help } from "@courselit/icons"; import { Slider } from "./components/ui/slider"; +import { Input } from "./components/ui/input"; import Tooltip from "./tooltip"; import { X } from "lucide-react"; import IconButton from "./icon-button"; @@ -34,10 +35,32 @@ export default function PageBuilderSlider({ )}
-
-

- {value} -

+
+
+
+ { + const raw = e.currentTarget.value; + const parsed = Number(raw); + if (Number.isNaN(parsed)) return; + const clamped = Math.min( + Math.max(parsed, min), + max, + ); + onChange(clamped); + }} + className="h-7 w-16 rounded-md border px-2 py-1 text-xs text-center bg-gray-50" + /> + + px + +
+
{allowsReset && ( -
+ +
{ e.preventDefault(); }} > - - setTitle(e.target.value)} + setTitle(e.target.value)} + /> +
+

Custom description

+ setDescription(state)} + showToolbar={false} + url={address.backend} /> -
-

- Custom description -

- setDescription(state)} - showToolbar={false} - url={address.backend} - /> -
- +
-
-
+ +
- + setButtonCaption(e.target.value)} + /> + {type === "site" && ( setButtonCaption(e.target.value)} + value={buttonAction} + label="Button Action (URL)" + onChange={(e) => setButtonAction(e.target.value)} /> - {type === "site" && ( + )} + {isLeadMagnet && ( + <> +
+

+ Success message +

+ + setSuccessMessage(state) + } + showToolbar={false} + url={address.backend} + /> +
- setButtonAction(e.target.value) + setFailureMessage(e.target.value) } /> - )} - {isLeadMagnet && ( - <> -
-

- Success message -

- - setSuccessMessage(state) - } - showToolbar={false} - url={address.backend} - /> -
- - setFailureMessage(e.target.value) - } - /> - + setEditingViewShowSuccess(value) + } + /> + + )} -
-
- - setTextAlignment(value)} - /> - - - - -
-
+ + + setTextAlignment(value)} + /> + + + + + ); } diff --git a/packages/page-blocks/src/blocks/content/admin-widget.tsx b/packages/page-blocks/src/blocks/content/admin-widget.tsx index 1c3ce48d6..164938a15 100644 --- a/packages/page-blocks/src/blocks/content/admin-widget.tsx +++ b/packages/page-blocks/src/blocks/content/admin-widget.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import Settings from "./settings"; import { AdminWidgetPanel, + AdminWidgetPanelContainer, Select, TextEditor, Form, @@ -51,8 +52,11 @@ export default function AdminWidget({ }, [title, description, headerAlignment, maxWidth, verticalPadding, cssId]); return ( -
- + +
setHeaderAlignment(value)} /> - + - + -
+ ); } diff --git a/packages/page-blocks/src/blocks/email-form/admin-widget.tsx b/packages/page-blocks/src/blocks/email-form/admin-widget.tsx index dd426fa72..503dd9516 100644 --- a/packages/page-blocks/src/blocks/email-form/admin-widget.tsx +++ b/packages/page-blocks/src/blocks/email-form/admin-widget.tsx @@ -4,6 +4,7 @@ import { AppDispatch } from "@courselit/state-management"; import type Settings from "./settings"; import { AdminWidgetPanel, + AdminWidgetPanelContainer, Select, Form, FormField, @@ -77,8 +78,11 @@ export default function AdminWidget({ ]); return ( -
- + + - +
{ e.preventDefault(); @@ -119,7 +123,7 @@ export default function AdminWidget({ />
- + + setContentType(value as "url" | "script") + } + options={[ + { label: "URL", value: "url" }, + { label: "Script", value: "script" }, + ]} + /> + + {contentType === "url" && ( + ) => + setContent(e.target.value) + } + /> + )} + + {contentType === "script" && ( +
+ +