diff --git a/app/lish/[did]/[publication]/PublicationContent.tsx b/app/lish/[did]/[publication]/PublicationContent.tsx new file mode 100644 index 00000000..5c2d9989 --- /dev/null +++ b/app/lish/[did]/[publication]/PublicationContent.tsx @@ -0,0 +1,185 @@ +import React from "react"; +import { AtUri } from "@atproto/syntax"; +import { + getPublicationURL, + getDocumentURL, +} from "app/lish/createPub/getPublicationURL"; +import { SubscribeWithBluesky } from "app/lish/Subscribe"; +import { + PublicationBackgroundProvider, + PublicationThemeProvider, +} from "components/ThemeManager/PublicationThemeProvider"; +import { InteractionPreview } from "components/InteractionsPreview"; +import { LocalizedDate } from "./LocalizedDate"; +import { PublicationHomeLayout } from "./PublicationHomeLayout"; +import { PublicationAuthor } from "./PublicationAuthor"; +import { + normalizePublicationRecord, + normalizeDocumentRecord, +} from "src/utils/normalizeRecords"; +import { getFirstParagraph } from "src/utils/getFirstParagraph"; +import { FontLoader } from "components/FontLoader"; +import { + PublicationHeader, + PublicationPostItem, +} from "./PublicationPageContent"; + +export const PublicationContent = ({ + record, + publication, + did, + profile, + showPageBackground, +}: { + record: ReturnType; + publication: { + uri: string; + name: string; + identity_did: string; + record: unknown; + publication_subscriptions: { identity: string }[]; + documents_in_publications: { + documents: { + uri: string; + data: unknown; + comments_on_documents: { count: number }[]; + document_mentions_in_bsky: { count: number }[]; + recommends_on_documents: { count: number }[]; + } | null; + }[]; + }; + did: string; + profile: { did: string; displayName?: string; handle: string } | undefined; + showPageBackground: boolean | undefined; +}) => { + return ( + <> + + + + + + ) : undefined + } + subscribeButton={ + + } + /> +
+ {publication.documents_in_publications + .filter((d) => !!d?.documents) + .sort((a, b) => { + const aRecord = normalizeDocumentRecord(a.documents?.data); + const bRecord = normalizeDocumentRecord(b.documents?.data); + const aDate = aRecord?.publishedAt + ? new Date(aRecord.publishedAt) + : new Date(0); + const bDate = bRecord?.publishedAt + ? new Date(bRecord.publishedAt) + : new Date(0); + return bDate.getTime() - aDate.getTime(); // Sort by most recent first + }) + .map((doc) => { + if (!doc.documents) return null; + const doc_record = normalizeDocumentRecord( + doc.documents.data, + ); + if (!doc_record) return null; + let uri = new AtUri(doc.documents.uri); + let quotes = + doc.documents.document_mentions_in_bsky[0].count || 0; + let comments = + record?.preferences?.showComments === false + ? 0 + : doc.documents.comments_on_documents[0].count || 0; + let recommends = + doc.documents.recommends_on_documents?.[0]?.count || 0; + let tags = doc_record.tags || []; + + const docUrl = getDocumentURL( + doc_record, + doc.documents.uri, + publication, + ); + return ( + + + ) : undefined + } + interactions={ + + } + /> + + ); + })} +
+
+
+
+ + ); +}; diff --git a/app/lish/[did]/[publication]/PublicationHomeLayout.tsx b/app/lish/[did]/[publication]/PublicationHomeLayout.tsx index 621119ff..1e4328bc 100644 --- a/app/lish/[did]/[publication]/PublicationHomeLayout.tsx +++ b/app/lish/[did]/[publication]/PublicationHomeLayout.tsx @@ -11,7 +11,7 @@ export function PublicationHomeLayout(props: { return (
+ {props.iconUrl && ( +
+ )} +

+ {props.publicationName} +

+

+ {props.description}{" "} +

+ {props.author} +
{props.subscribeButton}
+
+ ); +} + +export function PublicationPostItem(props: { + href?: string; + title?: string; + description?: string; + date?: React.ReactNode; + interactions?: React.ReactNode; +}) { + const content = ( + <> + {props.title &&

{props.title}

} +

+ {props.description} +

+ + ); + + return ( + <> +
+ {props.href ? ( + + {content} + + ) : ( +
+ {content} +
+ )} + +
+

+ {props.date}{" "} +

+ {props.interactions} +
+
+
+ + ); +} diff --git a/app/lish/[did]/[publication]/UpgradeModal.tsx b/app/lish/[did]/[publication]/UpgradeModal.tsx index f28f0576..ced03403 100644 --- a/app/lish/[did]/[publication]/UpgradeModal.tsx +++ b/app/lish/[did]/[publication]/UpgradeModal.tsx @@ -3,6 +3,7 @@ import { Modal } from "components/Modal"; import { useState } from "react"; import { createCheckoutSession } from "actions/createCheckoutSession"; import { DotLoader } from "components/utils/DotLoader"; +import { ToggleGroup } from "components/ToggleGroup"; export const UpgradeContent = () => { let [cadence, setCadence] = useState<"year" | "month">("year"); @@ -37,20 +38,15 @@ export const UpgradeContent = () => {
-
- - -
+
{cadence === "year" ? "$120" : "$12"} diff --git a/app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx b/app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx index 159300e3..76b5f7f9 100644 --- a/app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx +++ b/app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx @@ -41,8 +41,6 @@ export function LinearDocumentPage({ did, profile, preferences, - pubRecord, - theme, prerenderedCodeBlocks, bskyPostData, pollData, @@ -73,7 +71,10 @@ export function LinearDocumentPage({ pageOptions={pageOptions} footnoteSideColumn={ !props.hasContentToRight ? ( - + ) : undefined } > diff --git a/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx b/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx index 7aa1d731..c248e4dc 100644 --- a/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx +++ b/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx @@ -23,25 +23,6 @@ import { InlineUpgrade } from "../../UpgradeModal"; type SettingsView = "all" | "theme"; export function SettingsContent(props: { showPageBackground: boolean }) { - let [view, setView] = useState("all"); - let [themeLoading, setThemeLoading] = useState(false); - - if (view === "theme") { - return ( -
- setView("all")} - loading={themeLoading} - setLoading={setThemeLoading} - /> -
- ); - } - - return setView("theme")} />; -} - -function SettingsForm(props: { onOpenTheme: () => void }) { let { data } = usePublicationData(); let { publication: pubData } = data || {}; let isPro = useIsPro(); @@ -153,7 +134,7 @@ function SettingsForm(props: { onOpenTheme: () => void }) { {cardBorderHidden &&
} - + {cardBorderHidden &&
} diff --git a/app/lish/[did]/[publication]/dashboard/settings/ThemeSettings.tsx b/app/lish/[did]/[publication]/dashboard/settings/ThemeSettings.tsx index 6b07eff5..d0d0575e 100644 --- a/app/lish/[did]/[publication]/dashboard/settings/ThemeSettings.tsx +++ b/app/lish/[did]/[publication]/dashboard/settings/ThemeSettings.tsx @@ -1,16 +1,22 @@ +"use client"; + import { GoToArrow } from "components/Icons/GoToArrow"; +import { SpeedyLink } from "components/SpeedyLink"; +import { useParams } from "next/navigation"; + +export function ThemeSettings() { + let params = useParams<{ did: string; publication: string }>(); + let href = `/lish/${params.did}/${params.publication}/theme-settings`; -export function ThemeSettings(props: { onOpenTheme: () => void }) { return ( <>

Theme and Layout

- + ); } diff --git a/app/lish/[did]/[publication]/page.tsx b/app/lish/[did]/[publication]/page.tsx index c4fce3f1..790b8ea3 100644 --- a/app/lish/[did]/[publication]/page.tsx +++ b/app/lish/[did]/[publication]/page.tsx @@ -1,40 +1,24 @@ import { supabaseServerClient } from "supabase/serverClient"; -import { AtUri } from "@atproto/syntax"; import { getPublicationURL, getDocumentURL, } from "app/lish/createPub/getPublicationURL"; import { BskyAgent } from "@atproto/api"; import { publicationNameOrUriFilter } from "src/utils/uriHelpers"; -import { SubscribeWithBluesky } from "app/lish/Subscribe"; import React from "react"; -import { - PublicationBackgroundProvider, - PublicationThemeProvider, -} from "components/ThemeManager/PublicationThemeProvider"; import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; -import { SpeedyLink } from "components/SpeedyLink"; -import { InteractionPreview } from "components/InteractionsPreview"; -import { LocalizedDate } from "./LocalizedDate"; -import { PublicationHomeLayout } from "./PublicationHomeLayout"; -import { PublicationAuthor } from "./PublicationAuthor"; -import { Separator } from "components/Layout"; -import { - normalizePublicationRecord, - normalizeDocumentRecord, -} from "src/utils/normalizeRecords"; -import { getFirstParagraph } from "src/utils/getFirstParagraph"; -import { FontLoader } from "components/FontLoader"; +import { normalizePublicationRecord } from "src/utils/normalizeRecords"; +import { PublicationContent } from "./PublicationContent"; export default async function Publication(props: { params: Promise<{ publication: string; did: string }>; }) { let params = await props.params; - let did = decodeURIComponent(params.did); + const did = decodeURIComponent(params.did); if (!did) return ; - let agent = new BskyAgent({ service: "https://public.api.bsky.app" }); - let publication_name = decodeURIComponent(params.publication); - let [{ data: publications }, { data: profile }] = await Promise.all([ + const agent = new BskyAgent({ service: "https://public.api.bsky.app" }); + const publication_name = decodeURIComponent(params.publication); + const [{ data: publications }, { data: profile }] = await Promise.all([ supabaseServerClient .from("publications") .select( @@ -54,161 +38,23 @@ export default async function Publication(props: { .limit(1), agent.getProfile({ actor: did }), ]); - let publication = publications?.[0]; + const publication = publications?.[0]; const record = normalizePublicationRecord(publication?.record); - let showPageBackground = record?.theme?.showPageBackground; + const showPageBackground = record?.theme?.showPageBackground; if (!publication) return ; try { return ( <> - - - - -
- {record?.icon && ( -
- )} -

- {publication.name} -

-

- {record?.description}{" "} -

- {profile && ( - - )} -
- -
-
-
- {publication.documents_in_publications - .filter((d) => !!d?.documents) - .sort((a, b) => { - const aRecord = normalizeDocumentRecord(a.documents?.data); - const bRecord = normalizeDocumentRecord(b.documents?.data); - const aDate = aRecord?.publishedAt - ? new Date(aRecord.publishedAt) - : new Date(0); - const bDate = bRecord?.publishedAt - ? new Date(bRecord.publishedAt) - : new Date(0); - return bDate.getTime() - aDate.getTime(); // Sort by most recent first - }) - .map((doc) => { - if (!doc.documents) return null; - const doc_record = normalizeDocumentRecord( - doc.documents.data, - ); - if (!doc_record) return null; - let uri = new AtUri(doc.documents.uri); - let quotes = - doc.documents.document_mentions_in_bsky[0].count || 0; - let comments = - record?.preferences?.showComments === false - ? 0 - : doc.documents.comments_on_documents[0].count || 0; - let recommends = - doc.documents.recommends_on_documents?.[0]?.count || 0; - let tags = doc_record.tags || []; - - const docUrl = getDocumentURL( - doc_record, - doc.documents.uri, - publication, - ); - return ( - -
- - {doc_record.title && ( -

- {doc_record.title} -

- )} -

- {doc_record.description || - getFirstParagraph(doc_record)} -

-
- -
-

- {doc_record.publishedAt && ( - - )}{" "} -

- - -
-
-
-
- ); - })} -
- - - ); } catch (e) { diff --git a/app/lish/[did]/[publication]/theme-settings/PostPreview.tsx b/app/lish/[did]/[publication]/theme-settings/PostPreview.tsx new file mode 100644 index 00000000..5c592d32 --- /dev/null +++ b/app/lish/[did]/[publication]/theme-settings/PostPreview.tsx @@ -0,0 +1,162 @@ +"use client"; + +import { LinearDocumentPage } from "app/lish/[did]/[publication]/[rkey]/LinearDocumentPage"; +import { LeafletContentProvider } from "contexts/LeafletContentContext"; +import { DocumentProvider } from "contexts/DocumentContext"; +import { useIdentityData } from "components/IdentityProvider"; +import type { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; +import type { PubLeafletPagesLinearDocument } from "lexicons/api"; +import type { PostPageData } from "app/lish/[did]/[publication]/[rkey]/getPostPageData"; +import type { DocumentContextValue } from "contexts/DocumentContext"; +import { + usePublicationData, + useNormalizedPublicationRecord, +} from "../dashboard/PublicationSWRProvider"; + +const FAKE_DID = "did:plc:fake-preview-user"; +const FAKE_DOC_URI = + "at://did:plc:fake-preview-user/site.standard.document/preview"; + +const fakeBlocks: PubLeafletPagesLinearDocument.Block[] = [ + { + $type: "pub.leaflet.pages.linearDocument#block", + block: { + $type: "pub.leaflet.blocks.header", + level: 2, + plaintext: "A Heading", + }, + }, + { + $type: "pub.leaflet.pages.linearDocument#block", + block: { + $type: "pub.leaflet.blocks.text", + plaintext: + "This is a preview of what your posts will look like with the current theme settings. You can adjust colors, fonts, and layout options to match your style.", + }, + }, + { + $type: "pub.leaflet.pages.linearDocument#block", + block: { + $type: "pub.leaflet.blocks.text", + plaintext: + "Try changing the heading font, body font, accent color, and background to see how they affect the look and feel of your publication.", + }, + }, + { + $type: "pub.leaflet.pages.linearDocument#block", + block: { + $type: "pub.leaflet.blocks.horizontalRule", + }, + }, + { + $type: "pub.leaflet.pages.linearDocument#block", + block: { + $type: "pub.leaflet.blocks.text", + plaintext: + "Your readers will see posts rendered just like this. Make sure you're happy with the typography and spacing before publishing!", + }, + }, +]; + +const fakePage: PubLeafletPagesLinearDocument.Main = { + $type: "pub.leaflet.pages.linearDocument", + id: "preview-page", + blocks: fakeBlocks, +}; + +const fakeNormalizedDocument = { + $type: "site.standard.document" as const, + title: "Preview Post Title", + description: "A short description of this preview post.", + publishedAt: new Date().toISOString(), + site: "at://did:plc:fake-preview-user/site.standard.publication/preview", + tags: ["preview", "theme"], +}; + +const fakeDocument: NonNullable = { + data: {}, + uri: FAKE_DOC_URI, + normalizedDocument: fakeNormalizedDocument, + normalizedPublication: null, + quotesAndMentions: [], + theme: null, + prevNext: undefined, + publication: null, + comments: [], + comments_on_documents: [], + mentions: [], + document_mentions_in_bsky: [], + leaflets_in_publications: [], + leafletId: null, + recommendsCount: 0, + documents_in_publications: [], + recommends_on_documents: [], +} as unknown as NonNullable; + +const fakeDocumentContextValue: DocumentContextValue = { + uri: FAKE_DOC_URI, + normalizedDocument: fakeNormalizedDocument, + normalizedPublication: null, + theme: undefined, + prevNext: undefined, + quotesAndMentions: [], + publication: null, + comments: [], + mentions: [], + leafletId: null, + recommendsCount: 0, +}; + +export function PostPreview(props: { + showPageBackground: boolean; + pageWidth: number; +}) { + let { identity } = useIdentityData(); + let { data } = usePublicationData(); + let { publication } = data || {}; + let record = useNormalizedPublicationRecord(); + let preferences = record?.preferences; + let profileRecord = identity?.bsky_profiles + ?.record as unknown as ProfileViewDetailed; + + let profile = profileRecord ?? { + did: FAKE_DID, + handle: "preview.bsky.social", + displayName: "Preview Author", + }; + + return ( + + +
+ +
+
+
+ ); +} diff --git a/app/lish/[did]/[publication]/theme-settings/PubPreview.tsx b/app/lish/[did]/[publication]/theme-settings/PubPreview.tsx new file mode 100644 index 00000000..23e90e03 --- /dev/null +++ b/app/lish/[did]/[publication]/theme-settings/PubPreview.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { + usePublicationData, + useNormalizedPublicationRecord, +} from "app/lish/[did]/[publication]/dashboard/PublicationSWRProvider"; +import { useIdentityData } from "components/IdentityProvider"; +import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; +import { PublicationContent } from "../PublicationContent"; + +export function PubPreview(props: { + showPageBackground: boolean; + pageWidth: number; +}) { + let { data } = usePublicationData(); + let { publication } = data || {}; + let { identity } = useIdentityData(); + let record = useNormalizedPublicationRecord(); + + let profileRecord = identity?.bsky_profiles + ?.record as unknown as ProfileViewDetailed; + + let did = publication?.identity_did || ""; + + let profile = profileRecord + ? { + did: profileRecord.did, + displayName: profileRecord.displayName, + handle: profileRecord.handle, + } + : undefined; + + if (!publication) return null; + + return ( + + ); +} diff --git a/app/lish/[did]/[publication]/theme-settings/ThemeSettingsContent.tsx b/app/lish/[did]/[publication]/theme-settings/ThemeSettingsContent.tsx new file mode 100644 index 00000000..fa120eca --- /dev/null +++ b/app/lish/[did]/[publication]/theme-settings/ThemeSettingsContent.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { useState } from "react"; +import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; +import { ButtonPrimary } from "components/Buttons"; +import { DotLoader } from "components/utils/DotLoader"; +import { ToggleGroup } from "components/ToggleGroup"; +import { PaintSmall } from "components/Icons/PaintSmall"; +import { Popover } from "components/Popover"; +import { + usePubThemeEditorState, + PubThemePickerPanel, +} from "components/ThemeManager/PubThemeSetter"; +import { PubPreview } from "./PubPreview"; +import { PostPreview } from "./PostPreview"; + +export function ThemeSettingsContent() { + let [previewMode, setPreviewMode] = useState<"post" | "pub">("post"); + let [loading, setLoading] = useState(false); + + let state = usePubThemeEditorState(); + let { + localPubTheme, + headingFont, + bodyFont, + image, + pageWidth, + pubBGImage, + leafletBGRepeat, + showPageBackground, + submitTheme, + toaster, + } = state; + + return ( + +
+ {/* Theme Setter Panel */} +
+ + + + } + asChild + > +
{ + e.preventDefault(); + let result = await submitTheme(setLoading); + if (result?.success) { + toaster({ content: "Theme updated!", type: "success" }); + } + }} + > + {/* Toggle + Save Header */} +
+
Preview Mode
+ +
+ +
+ +
+
+ + {loading ? : "Update"} + +
+
+
+
+ + {/* Full-page Preview */} +
+
+
+ {previewMode === "pub" ? ( + + ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/app/lish/[did]/[publication]/theme-settings/page.tsx b/app/lish/[did]/[publication]/theme-settings/page.tsx new file mode 100644 index 00000000..f668745c --- /dev/null +++ b/app/lish/[did]/[publication]/theme-settings/page.tsx @@ -0,0 +1,69 @@ +import { supabaseServerClient } from "supabase/serverClient"; +import { getIdentityData } from "actions/getIdentityData"; +import { get_publication_data } from "app/api/rpc/[command]/get_publication_data"; +import { PublicationSWRDataProvider } from "../dashboard/PublicationSWRProvider"; +import { PublicationThemeProviderDashboard } from "components/ThemeManager/PublicationThemeProvider"; +import { AtUri } from "@atproto/syntax"; +import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; +import { normalizePublicationRecord } from "src/utils/normalizeRecords"; +import { ThemeSettingsContent } from "./ThemeSettingsContent"; + +export default async function ThemeSettingsPage(props: { + params: Promise<{ publication: string; did: string }>; +}) { + let params = await props.params; + let identity = await getIdentityData(); + if (!identity || !identity.atp_did) + return ( + +

Looks like you're not logged in.

+

+ If the issue persists please{" "} + send us a note. +

+
+ ); + let did = decodeURIComponent(params.did); + if (!did) return ; + let { result: publication_data } = await get_publication_data.handler( + { + did, + publication_name: decodeURIComponent(params.publication), + }, + { supabase: supabaseServerClient }, + ); + let { publication } = publication_data; + const record = normalizePublicationRecord(publication?.record); + + if (!publication || identity.atp_did !== publication.identity_did || !record) + return ; + let uri = new AtUri(publication.uri); + + try { + return ( + + + + + + ); + } catch (e) { + return
{JSON.stringify(e, undefined, 2)}
; + } +} + +const ThemeNotFound = () => { + return ( + +

Sorry, we can't find this publication!

+

+ This may be a glitch on our end. If the issue persists please{" "} + send us a note. +

+
+ ); +}; diff --git a/components/ThemeManager/PubThemeSetter.tsx b/components/ThemeManager/PubThemeSetter.tsx index 72150cef..1e0a1592 100644 --- a/components/ThemeManager/PubThemeSetter.tsx +++ b/components/ThemeManager/PubThemeSetter.tsx @@ -29,12 +29,8 @@ export type ImageState = { file?: File; repeat: number | null; }; -export const PubThemeSetter = (props: { - backToMenu: () => void; - loading: boolean; - setLoading: (l: boolean) => void; -}) => { - let [sample, setSample] = useState<"pub" | "post">("pub"); + +export function usePubThemeEditorState() { let [openPicker, setOpenPicker] = useState("null"); let { data, mutate } = usePublicationData(); let { publication: pub } = data || {}; @@ -73,6 +69,216 @@ export const PubThemeSetter = (props: { let leafletBGRepeat = image?.repeat || null; let toaster = useToaster(); + let submitTheme = async (setLoading: (l: boolean) => void) => { + if (!pub) return; + setLoading(true); + let result = await updatePublicationTheme({ + uri: pub.uri, + theme: { + pageBackground: ColorToRGBA(localPubTheme.bgPage), + showPageBackground: showPageBackground, + backgroundColor: image + ? ColorToRGBA(localPubTheme.bgLeaflet) + : ColorToRGB(localPubTheme.bgLeaflet), + backgroundRepeat: image?.repeat, + backgroundImage: image ? image.file : null, + pageWidth: pageWidth, + primary: ColorToRGB(localPubTheme.primary), + accentBackground: ColorToRGB(localPubTheme.accent1), + accentText: ColorToRGB(localPubTheme.accent2), + headingFont: headingFont, + bodyFont: bodyFont, + }, + }); + + if (!result.success) { + setLoading(false); + if (result.error && isOAuthSessionError(result.error)) { + toaster({ + content: , + type: "error", + }); + } else { + toaster({ + content: "Failed to update theme", + type: "error", + }); + } + return result; + } + + mutate((pub) => { + if (result.publication && pub?.publication) + return { + ...pub, + publication: { ...pub.publication, ...result.publication }, + }; + return pub; + }, false); + setLoading(false); + return result; + }; + + return { + openPicker, + setOpenPicker, + pub, + record, + mutate, + showPageBackground, + setShowPageBackground, + localPubTheme, + setTheme, + changes, + image, + setImage, + pageWidth, + setPageWidth, + headingFont, + setHeadingFont, + bodyFont, + setBodyFont, + pubBGImage, + leafletBGRepeat, + toaster, + submitTheme, + }; +} + +export type PubThemeEditorState = ReturnType; + +export function PubThemePickerPanel(props: { state: PubThemeEditorState }) { + let { + openPicker, + setOpenPicker, + showPageBackground, + setShowPageBackground, + localPubTheme, + setTheme, + image, + setImage, + pageWidth, + setPageWidth, + headingFont, + setHeadingFont, + bodyFont, + setBodyFont, + pubBGImage, + leafletBGRepeat, + } = props.state; + + return ( +
+ +
+
+
+ { + setTheme((t) => ({ ...t, bgPage: color })); + }} + setBackgroundColor={(color) => { + setTheme((t) => ({ ...t, bgLeaflet: color })); + }} + openPicker={openPicker} + setOpenPicker={setOpenPicker} + hasPageBackground={!!showPageBackground} + setHasPageBackground={setShowPageBackground} + /> +
+ + +
+
+ +
+
+ { + setTheme((t) => ({ ...t, bgPage: color })); + }} + setPrimary={(color) => { + setTheme((t) => ({ ...t, primary: color })); + }} + openPicker={openPicker} + setOpenPicker={(pickers) => setOpenPicker(pickers)} + hasPageBackground={showPageBackground} + /> +
+ + +
+ { + setTheme((t) => ({ ...t, accent1: color })); + }} + accent2={localPubTheme.accent2} + setAccent2={(color) => { + setTheme((t) => ({ ...t, accent2: color })); + }} + openPicker={openPicker} + setOpenPicker={(pickers) => setOpenPicker(pickers)} + /> +
+
+
+ ); +} + +export const PubThemeSetter = (props: { + backToMenu: () => void; + loading: boolean; + setLoading: (l: boolean) => void; +}) => { + let [sample, setSample] = useState<"pub" | "post">("pub"); + let state = usePubThemeEditorState(); + let { + localPubTheme, + headingFont, + bodyFont, + image, + pageWidth, + pubBGImage, + leafletBGRepeat, + pub, + record, + showPageBackground, + submitTheme, + } = state; + return ( { e.preventDefault(); - if (!pub) return; - props.setLoading(true); - let result = await updatePublicationTheme({ - uri: pub.uri, - theme: { - pageBackground: ColorToRGBA(localPubTheme.bgPage), - showPageBackground: showPageBackground, - backgroundColor: image - ? ColorToRGBA(localPubTheme.bgLeaflet) - : ColorToRGB(localPubTheme.bgLeaflet), - backgroundRepeat: image?.repeat, - backgroundImage: image ? image.file : null, - pageWidth: pageWidth, - primary: ColorToRGB(localPubTheme.primary), - accentBackground: ColorToRGB(localPubTheme.accent1), - accentText: ColorToRGB(localPubTheme.accent2), - headingFont: headingFont, - bodyFont: bodyFont, - }, - }); - - if (!result.success) { - props.setLoading(false); - if (result.error && isOAuthSessionError(result.error)) { - toaster({ - content: , - type: "error", - }); - } else { - toaster({ - content: "Failed to update theme", - type: "error", - }); - } - return; - } - - mutate((pub) => { - if (result.publication && pub?.publication) - return { - ...pub, - publication: { ...pub.publication, ...result.publication }, - }; - return pub; - }, false); - props.setLoading(false); + await submitTheme(props.setLoading); }} > - update
- -
-
-
- { - setTheme((t) => ({ ...t, bgPage: color })); - }} - setBackgroundColor={(color) => { - setTheme((t) => ({ ...t, bgLeaflet: color })); - }} - openPicker={openPicker} - setOpenPicker={setOpenPicker} - hasPageBackground={!!showPageBackground} - setHasPageBackground={setShowPageBackground} - /> -
- - -
-
- -
-
- { - setTheme((t) => ({ ...t, bgPage: color })); - }} - setPrimary={(color) => { - setTheme((t) => ({ ...t, primary: color })); - }} - openPicker={openPicker} - setOpenPicker={(pickers) => setOpenPicker(pickers)} - hasPageBackground={showPageBackground} - /> -
- - -
- { - setTheme((t) => ({ ...t, accent1: color })); - }} - accent2={localPubTheme.accent2} - setAccent2={(color) => { - setTheme((t) => ({ ...t, accent2: color })); - }} - openPicker={openPicker} - setOpenPicker={(pickers) => setOpenPicker(pickers)} - /> -
-
+
Preview
@@ -248,19 +320,6 @@ export const PubThemeSetter = (props: { Post
- {sample === "pub" ? ( - - ) : ( - - )}
diff --git a/components/ToggleGroup.tsx b/components/ToggleGroup.tsx new file mode 100644 index 00000000..74c07f51 --- /dev/null +++ b/components/ToggleGroup.tsx @@ -0,0 +1,32 @@ +import { type ReactNode } from "react"; + +export function ToggleGroup(props: { + value: T; + onChange: (value: T) => void; + options: { value: T; label: ReactNode }[]; + className?: string; + optionClassName?: string; +}) { + return ( +
+ {props.options.map((option) => ( + + ))} +
+ ); +}