diff --git a/apps/web/app/(with-contexts)/dashboard/(sidebar)/settings/page.tsx b/apps/web/app/(with-contexts)/dashboard/(sidebar)/settings/page.tsx index 7fe42acb4..4111f5776 100644 --- a/apps/web/app/(with-contexts)/dashboard/(sidebar)/settings/page.tsx +++ b/apps/web/app/(with-contexts)/dashboard/(sidebar)/settings/page.tsx @@ -3,11 +3,7 @@ import DashboardContent from "@components/admin/dashboard-content"; import LoadingScreen from "@components/admin/loading-screen"; import Settings from "@components/admin/settings"; -import { - AddressContext, - ProfileContext, - SiteInfoContext, -} from "@components/contexts"; +import { ProfileContext, SiteInfoContext } from "@components/contexts"; import { Profile, UIConstants } from "@courselit/common-models"; import { checkPermission } from "@courselit/utils"; import { SITE_SETTINGS_PAGE_HEADING } from "@ui-config/strings"; @@ -19,7 +15,6 @@ const breadcrumbs = [{ label: SITE_SETTINGS_PAGE_HEADING, href: "#" }]; export default function Page() { const siteinfo = useContext(SiteInfoContext); - const address = useContext(AddressContext); const { profile } = useContext(ProfileContext); const searchParams = useSearchParams(); @@ -37,12 +32,9 @@ export default function Page() { {}} loading={false} - networkAction={false} /> ); diff --git a/apps/web/app/(with-contexts)/dashboard/page/[id]/page.tsx b/apps/web/app/(with-contexts)/dashboard/page/[id]/page.tsx index 74ae230eb..76502deaa 100644 --- a/apps/web/app/(with-contexts)/dashboard/page/[id]/page.tsx +++ b/apps/web/app/(with-contexts)/dashboard/page/[id]/page.tsx @@ -44,7 +44,7 @@ export default function Page(props: { params: Promise<{ id: string }> }) { siteinfo: siteInfo, address: address, profile: profile as Profile, - auth: profile.email + auth: profile!.email ? { guest: false, checked: true, @@ -72,7 +72,6 @@ export default function Page(props: { params: Promise<{ id: string }> }) { action: null, }, }} - dispatch={() => {}} /> ); } diff --git a/apps/web/components/admin/page-editor/index.tsx b/apps/web/components/admin/page-editor/index.tsx index 88f5e1eee..1e1e44ae9 100644 --- a/apps/web/components/admin/page-editor/index.tsx +++ b/apps/web/components/admin/page-editor/index.tsx @@ -519,52 +519,6 @@ export default function PageEditor({ [selectedWidget], ); - // const saveDraftTypefaces = async (fontName: string) => { - // const newTypefaces: Typeface[] = structuredClone(draftTypefaces); - // const defaultSection = newTypefaces.filter( - // (x) => x.section === "default", - // )[0]; - // defaultSection.typeface = fontName; - - // const query = ` - // mutation { - // site: updateDraftTypefaces( - // typefaces: ${getGraphQLQueryStringFromObject(newTypefaces)} - // ) { - // draftTypefaces { - // section, - // typeface, - // fontWeights, - // fontSize, - // lineHeight, - // letterSpacing, - // case - // }, - // } - // } - // `; - // const fetch = new FetchBuilder() - // .setUrl(`${address.backend}/api/graph`) - // .setPayload(query) - // .setIsGraphQLEndpoint(true) - // .build(); - // try { - // dispatch && dispatch(networkAction(true)); - // const response = await fetch.exec(); - // if (response.site) { - // setDraftTypefaces(response.site.draftTypefaces); - // } - // } catch (err: any) { - // toast({ - // title: TOAST_TITLE_ERROR, - // description: err.message, - // variant: "destructive", - // }); - // } finally { - // dispatch && dispatch(networkAction(false)); - // } - // }; - const onAddWidgetBelow = (index: number) => { setSelectedWidgetIndex(index); setLeftPaneContent("widgets"); @@ -762,6 +716,7 @@ export default function PageEditor({ onClick={onPublish} size="sm" className="gap-2 whitespace-nowrap" + disabled={loading} > {EDIT_PAGE_BUTTON_UPDATE} diff --git a/apps/web/components/admin/settings/index.tsx b/apps/web/components/admin/settings/index.tsx index 73d68f462..ea9d79886 100644 --- a/apps/web/components/admin/settings/index.tsx +++ b/apps/web/components/admin/settings/index.tsx @@ -135,35 +135,6 @@ const Settings = (props: SettingsProps) => { loadAdminSettings(); }, []); - // useEffect(() => { - // props.dispatch( - // newSiteInfoAvailable({ - // title: settings.title || "", - // subtitle: settings.subtitle || "", - // logo: settings.logo, - // currencyISOCode: settings.currencyISOCode, - // paymentMethod: settings.paymentMethod, - // stripeKey: settings.stripeKey, - // codeInjectionHead: settings.codeInjectionHead - // ? encode(settings.codeInjectionHead) - // : "", - // codeInjectionBody: settings.codeInjectionBody - // ? encode(settings.codeInjectionBody) - // : "", - // mailingAddress: settings.mailingAddress || "", - // hideCourseLitBranding: settings.hideCourseLitBranding ?? false, - // razorpayKey: settings.razorpayKey, - // lemonsqueezyStoreId: settings.lemonsqueezyStoreId, - // lemonsqueezyOneTimeVariantId: - // settings.lemonsqueezyOneTimeVariantId, - // lemonsqueezySubscriptionMonthlyVariantId: - // settings.lemonsqueezySubscriptionMonthlyVariantId, - // lemonsqueezySubscriptionYearlyVariantId: - // settings.lemonsqueezySubscriptionYearlyVariantId, - // }), - // ); - // }, [settings]); - const loadAdminSettings = async () => { const query = ` query { diff --git a/apps/web/components/public/base-layout/template/index.tsx b/apps/web/components/public/base-layout/template/index.tsx index 2659b1183..eb4dc22ba 100644 --- a/apps/web/components/public/base-layout/template/index.tsx +++ b/apps/web/components/public/base-layout/template/index.tsx @@ -73,7 +73,7 @@ const Template = (props: TemplateProps) => { ); return ( -
+
{header && ( By signing in, you accept our{" "} - Terms - {" "} + {" "} and{" "} - Privacy Policy - + -
- ); -}; -``` - -This will reflect your changes in the `Edit Widget` component. - -## Server Side Rendering (SSR) - -It is recommended to fetch the data required for showing the widget, on the server side. You can request data from CourseLit's GraphQL API by attaching a method called `getData` to your `widget` component. - -While loading the app, all such methods from all the used widgets across the app will be combined and executed as a single query to reduce the round trips to the server. - -The data fetched from the server will be stored in the `widgetsData` property of the app state. Every query will get a unique id in the combined query so that while displaying your widget you can pull out the right data from `widgetsData`. - -The `getData` method has the following signature. - -```js -YourComponent.getData(widgetId: string, widgetSettings: Record) => string; -``` - -You will get a unique `widgetId` from the framework. You have to use this as a key to your query. In your React components (widget and adminWidget) you will get this id in a prop called `id`. - -### Example - -```js -// Fetches courses with a certain tag -Widget.getData = (id: string, settings: Record) => ` - ${id}: getProducts(offset: 1, tag: "${settings && settings.tag}") { - id, - title, - cost, - featuredImage { - thumbnail - }, - slug, - courseId, - isBlog, - description - } -`; -``` - -## Theming - -CourseLit uses [Material-UI's Theming](https://material-ui.com/customization/theming/) system hence you can introduce additional [custom variables](https://material-ui.com/customization/theming/#custom-variables) to the app's theme which you can later consume in your widget. - -> Make sure there is a default styling as other themes may or may not provide the custom variables required by your Widget. - -To learn how to design themes for CourseLit, see this [link](https://codelit.gitbook.io/courselit/administration-1/layout-and-themes#themes). - -## Shared Widgets - -Shared widgets are those whose settings are stored on the domain level, instead of page level. Hence, a user is not required to configure a shared widget individually for every single page it is used on. - -Any page can use a shared widget but the widget's settings are going to be saved and retrieved from the domain. - -⚠️ A shared widget's settings are immediately published and are reflected on all pages. There is no draft mode for shared widgets. - -## Something's Not Clear? - -Come chat with us in our [official Discord channel](https://discord.com/invite/GR4bQsN). diff --git a/packages/common-models/src/constants.ts b/packages/common-models/src/constants.ts index cb447ac2c..fd92a8138 100644 --- a/packages/common-models/src/constants.ts +++ b/packages/common-models/src/constants.ts @@ -36,8 +36,8 @@ export const leads = ["website", "newsletter", "download", "api"] as const; export const mailRequestStatus = ["pending", "approved", "rejected"] as const; export const pageNames = { home: "Home page", - terms: "Terms of Service", - privacy: "Privacy policy", + terms: "Terms of Use", + privacy: "Privacy Policy", blog: "Blog", }; export const dripType = ["relative-date", "exact-date"] as const; diff --git a/packages/page-blocks/src/blocks/faq/admin-widget/index.tsx b/packages/page-blocks/src/blocks/faq/admin-widget/index.tsx index c3638aebd..c5e69b412 100644 --- a/packages/page-blocks/src/blocks/faq/admin-widget/index.tsx +++ b/packages/page-blocks/src/blocks/faq/admin-widget/index.tsx @@ -14,7 +14,11 @@ import { CssIdField, MaxWidthSelector, VerticalPaddingSelector, + DragAndDrop, + IconButton, } from "@courselit/components-library"; +import { Edit } from "@courselit/icons"; +import { generateUniqueId } from "@courselit/utils"; export interface AdminWidgetProps { settings: Settings; @@ -39,7 +43,7 @@ export default function AdminWidget({ hideActionButtons, preservedStateAcrossRerender, theme, -}: AdminWidgetProps): JSX.Element { +}: AdminWidgetProps) { const dummyDescription: Record = { type: "doc", content: [ @@ -82,7 +86,8 @@ export default function AdminWidget({ const [headerAlignment, setHeaderAlignment] = useState( settings.headerAlignment || "center", ); - const [itemBeingEditedIndex, setItemBeingEditedIndex] = useState(-1); + const [itemBeingEditedIndex, setItemBeingEditedIndex] = + useState(-1); const [maxWidth, setMaxWidth] = useState< ThemeStyle["structure"]["page"]["width"] >(settings.maxWidth); @@ -90,6 +95,7 @@ export default function AdminWidget({ ThemeStyle["structure"]["section"]["padding"]["y"] >(settings.verticalPadding); const [cssId, setCssId] = useState(settings.cssId); + const [layout, setLayout] = useState(settings.layout || "vertical"); const onSettingsChanged = () => onChange({ @@ -100,6 +106,8 @@ export default function AdminWidget({ maxWidth, verticalPadding, cssId, + itemBeingEditedIndex, + layout, }); useEffect(() => { @@ -112,6 +120,8 @@ export default function AdminWidget({ maxWidth, verticalPadding, cssId, + itemBeingEditedIndex, + layout, ]); const onItemChange = (newItemData: Item) => { @@ -179,21 +189,36 @@ export default function AdminWidget({ -
    - {items.map((item: Item, index: number) => ( -
  • { - hideActionButtons(true, { - selectedItem: index, - }); - }} - className="p-1 border border-transparent hover:border-slate-300 rounded" - > - {item.title} -
  • - ))} -
+ ({ + item, + id: generateUniqueId(), + }))} + Renderer={({ item }) => ( +
+

{item.title}

+ { + hideActionButtons(true, { + selectedItem: items.findIndex( + (i) => i.title === item.title, + ), + }); + }} + > + + +
+ )} + onChange={(newItems: { item: Item }[]) => { + const itemsInNewOrder: Item[] = []; + for (const item of newItems) { + itemsInNewOrder.push(Object.assign({}, item.item)); + } + setItems(itemsInNewOrder); + }} + />
+ {buttonCaption} + + + )} + {secondaryButtonAction && secondaryButtonCaption && ( + + + )} -
- {buttonAction && buttonCaption && ( - - - - )} - {secondaryButtonAction && - secondaryButtonCaption && ( - - - - )} -
+ + ); + + return ( +
+ {layout === "normal" ? ( + mainContent + ) : ( + + + {mainContent} + + + )}
); } diff --git a/packages/page-blocks/src/blocks/media/admin-widget.tsx b/packages/page-blocks/src/blocks/media/admin-widget.tsx index d91db546e..984c49a6f 100644 --- a/packages/page-blocks/src/blocks/media/admin-widget.tsx +++ b/packages/page-blocks/src/blocks/media/admin-widget.tsx @@ -16,9 +16,11 @@ import { Select, MaxWidthSelector, VerticalPaddingSelector, + Tooltip, } from "@courselit/components-library"; import { isVideo } from "@courselit/utils"; import type { Theme, ThemeStyle } from "@courselit/page-models"; +import { Help } from "@courselit/icons"; interface AdminWidgetProps { name: string; @@ -58,6 +60,9 @@ export default function AdminWidget({ const [objectFit, setObjectFit] = useState( settings.objectFit || "cover", ); + const [hasBorder, setHasBorder] = useState( + settings.hasBorder || true, + ); const onSettingsChanged = () => onChange({ @@ -70,6 +75,7 @@ export default function AdminWidget({ playVideoInModal, aspectRatio, objectFit, + hasBorder, }); useEffect(() => { @@ -84,6 +90,7 @@ export default function AdminWidget({ playVideoInModal, aspectRatio, objectFit, + hasBorder, ]); return ( @@ -188,6 +195,18 @@ export default function AdminWidget({ } onChange={setVerticalPadding} /> +
+
+

Border

+ + + +
+ setHasBorder(value)} + /> +
diff --git a/packages/page-blocks/src/blocks/media/settings.ts b/packages/page-blocks/src/blocks/media/settings.ts index d1abb14ed..0c7c3f862 100644 --- a/packages/page-blocks/src/blocks/media/settings.ts +++ b/packages/page-blocks/src/blocks/media/settings.ts @@ -8,4 +8,5 @@ export default interface Settings extends WidgetDefaultSettings { playVideoInModal?: boolean; aspectRatio?: AspectRatio; objectFit?: ImageObjectFit; + hasBorder?: boolean; } diff --git a/packages/page-blocks/src/blocks/media/widget.tsx b/packages/page-blocks/src/blocks/media/widget.tsx index bc5158d8d..de8237fd4 100644 --- a/packages/page-blocks/src/blocks/media/widget.tsx +++ b/packages/page-blocks/src/blocks/media/widget.tsx @@ -30,6 +30,7 @@ export default function Widget({ objectFit, maxWidth, verticalPadding, + hasBorder = true, }, state: { theme }, }: WidgetProps) { @@ -44,49 +45,34 @@ export default function Widget({ return (
- {hasHeroGraphic && ( -
-
- {isVideo(youtubeLink, media) ? ( - - ) : ( - {media?.caption - )} -
-
- )} - {!hasHeroGraphic && ( -
- -
- )} +
+ {isVideo(youtubeLink, media) ? ( + + ) : ( + {media?.caption + )} +
); diff --git a/packages/tailwind-config/tailwind.config.ts b/packages/tailwind-config/tailwind.config.ts index 394e624bf..4e7a8ccd8 100644 --- a/packages/tailwind-config/tailwind.config.ts +++ b/packages/tailwind-config/tailwind.config.ts @@ -358,6 +358,11 @@ const config: Config = { pattern: /border-(solid|dashed|dotted|double|none)/, variants: ["hover"], }, + "backdrop-blur-2xl", + "border-b", + { + pattern: /border-b-(0|2|4|8)/, + }, { pattern: /shadow-(sm|md|lg|xl|2xl|inner|none)/, variants: ["hover", "dark"],