diff --git a/app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx b/app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx index 23b3829a..61510f54 100644 --- a/app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx +++ b/app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx @@ -14,7 +14,8 @@ import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; import { type NormalizedPublication } from "src/utils/normalizeRecords"; import { PublicationAnalytics } from "./PublicationAnalytics"; import { useCanSeePro } from "src/hooks/useEntitlement"; -import { SettingsTab } from "./settings/SettingsTab"; +import { SettingsContent } from "./settings/SettingsContent"; +import { SettingsTiny } from "components/Icons/SettingsTiny"; export default function PublicationDashboard({ publication, @@ -68,7 +69,7 @@ export default function PublicationDashboard({ ), controls: null, }, - Subscribers: { + Subs: { content: ( , content: ( - ), diff --git a/app/lish/[did]/[publication]/dashboard/settings/GeneralSettings.tsx b/app/lish/[did]/[publication]/dashboard/settings/GeneralSettings.tsx new file mode 100644 index 00000000..6c2f8f18 --- /dev/null +++ b/app/lish/[did]/[publication]/dashboard/settings/GeneralSettings.tsx @@ -0,0 +1,85 @@ +import { useRef } from "react"; +import { Input } from "components/Input"; +import { AddTiny } from "components/Icons/AddTiny"; + +export function GeneralSettings(props: { + nameValue: string; + setNameValue: (v: string) => void; + descriptionValue: string; + setDescriptionValue: (v: string) => void; + iconPreview: string | null; + setIconPreview: (v: string | null) => void; + setIconFile: (f: File | null) => void; +}) { + let fileInputRef = useRef(null); + + return ( +
+

General Settings

+ +
+

+ Logo (optional) +

+
fileInputRef.current?.click()} + > + {props.iconPreview ? ( + Logo preview + ) : ( + + )} +
+ { + const file = e.target.files?.[0]; + if (file) { + props.setIconFile(file); + const reader = new FileReader(); + reader.onload = (ev) => { + props.setIconPreview(ev.target?.result as string); + }; + reader.readAsDataURL(file); + } + }} + /> +
+ + + + +
+ ); +} diff --git a/app/lish/[did]/[publication]/dashboard/settings/PostSettings.tsx b/app/lish/[did]/[publication]/dashboard/settings/PostSettings.tsx new file mode 100644 index 00000000..c0ed9d9e --- /dev/null +++ b/app/lish/[did]/[publication]/dashboard/settings/PostSettings.tsx @@ -0,0 +1,83 @@ +import { Toggle } from "components/Toggle"; + +export function PostSettings(props: { + showComments: boolean; + setShowComments: (v: boolean) => void; + showMentions: boolean; + setShowMentions: (v: boolean) => void; + showRecommends: boolean; + setShowRecommends: (v: boolean) => void; + showPrevNext: boolean; + setShowPrevNext: (v: boolean) => void; + showInDiscover: boolean; + setShowInDiscover: (v: boolean) => void; +}) { + return ( +
+

Post Settings

+ +

Discovery

+ props.setShowInDiscover(!props.showInDiscover)} + > +
+
Make Public
+
+ Your posts will appear in{" "} + + Leaflet Reader + {" "} + and show up in search and tags. +
+
+
+ +
+ +

Layout

+ props.setShowPrevNext(!props.showPrevNext)} + > +
Show Prev/Next Buttons
+
+ +
+ +

Interactions

+
+ props.setShowComments(!props.showComments)} + > +
Show Comments
+
+ + props.setShowMentions(!props.showMentions)} + > +
+
Show Mentions
+
+ Display a list of Bluesky mentions about your post +
+
+
+ + props.setShowRecommends(!props.showRecommends)} + > +
+
Show Recommends
+
+ Allow readers to recommend/like your post +
+
+
+
+
+ ); +} diff --git a/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx b/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx new file mode 100644 index 00000000..f83277c3 --- /dev/null +++ b/app/lish/[did]/[publication]/dashboard/settings/SettingsContent.tsx @@ -0,0 +1,166 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { ButtonPrimary } from "components/Buttons"; +import { DotLoader } from "components/utils/DotLoader"; +import { useToaster } from "components/Toast"; +import { mutate } from "swr"; +import { + usePublicationData, + useNormalizedPublicationRecord, +} from "../PublicationSWRProvider"; +import { updatePublication } from "app/lish/createPub/updatePublication"; +import { DomainsInline } from "./DomainsInline"; +import { PubThemeSetter } from "components/ThemeManager/PubThemeSetter"; +import { GeneralSettings } from "./GeneralSettings"; +import { PostSettings } from "./PostSettings"; +import { ThemeSettings } from "./ThemeSettings"; + +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 record = useNormalizedPublicationRecord(); + let [loading, setLoading] = useState(false); + let toast = useToaster(); + + // --- General Settings state --- + let [nameValue, setNameValue] = useState(record?.name || ""); + let [descriptionValue, setDescriptionValue] = useState( + record?.description || "", + ); + let [iconFile, setIconFile] = useState(null); + let [iconPreview, setIconPreview] = useState(null); + + let [showInDiscover, setShowInDiscover] = useState( + record?.preferences?.showInDiscover === undefined + ? true + : record.preferences.showInDiscover, + ); + + // --- Post Settings state --- + let [showComments, setShowComments] = useState( + record?.preferences?.showComments === undefined + ? true + : record.preferences.showComments, + ); + let [showMentions, setShowMentions] = useState( + record?.preferences?.showMentions === undefined + ? true + : record.preferences.showMentions, + ); + let [showRecommends, setShowRecommends] = useState( + record?.preferences?.showRecommends === undefined + ? true + : record.preferences.showRecommends, + ); + let [showPrevNext, setShowPrevNext] = useState( + record?.preferences?.showPrevNext === undefined + ? true + : record.preferences.showPrevNext, + ); + + // Sync from server data + useEffect(() => { + if (!pubData || !pubData.record || !record) return; + setNameValue(record.name); + setDescriptionValue(record.description || ""); + if (record.icon) + setIconPreview( + `/api/atproto_images?did=${pubData.identity_did}&cid=${(record.icon.ref as unknown as { $link: string })["$link"]}`, + ); + }, [pubData, record]); + + return ( +
{ + e.preventDefault(); + if (!pubData) return; + setLoading(true); + await updatePublication({ + uri: pubData.uri, + name: nameValue, + description: descriptionValue, + iconFile: iconFile, + preferences: { + showInDiscover, + showComments, + showMentions, + showPrevNext, + showRecommends, + }, + }); + toast({ type: "success", content: "Settings saved!" }); + setLoading(false); + mutate("publication-data"); + }} + > +
+ {/* ── General Settings ── */} + + +
+ + {/* ── Post Settings ── */} + + +
+ + {/* ── Theme ── */} + + +
+ + {/* ── Domains ── */} + +
+ + {/* ── Sticky Save Footer ── */} +
+ + {loading ? : "Save Changes"} + +
+
+ ); +} diff --git a/app/lish/[did]/[publication]/dashboard/settings/SettingsTab.tsx b/app/lish/[did]/[publication]/dashboard/settings/SettingsTab.tsx deleted file mode 100644 index 492a4a41..00000000 --- a/app/lish/[did]/[publication]/dashboard/settings/SettingsTab.tsx +++ /dev/null @@ -1,285 +0,0 @@ -"use client"; - -import { useState, useRef, useEffect } from "react"; -import { Input } from "components/Input"; -import { Toggle } from "components/Toggle"; -import { ButtonPrimary } from "components/Buttons"; -import { DotLoader } from "components/utils/DotLoader"; -import { AddTiny } from "components/Icons/AddTiny"; -import { useToaster } from "components/Toast"; -import { mutate } from "swr"; -import { - usePublicationData, - useNormalizedPublicationRecord, -} from "../PublicationSWRProvider"; -import { updatePublication } from "app/lish/createPub/updatePublication"; -import { DomainsInline } from "./DomainsInline"; -import { PubThemeSetter } from "components/ThemeManager/PubThemeSetter"; - -type SettingsView = "all" | "theme"; - -export function SettingsTab(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 record = useNormalizedPublicationRecord(); - let [loading, setLoading] = useState(false); - let toast = useToaster(); - - // --- General Settings state --- - let [nameValue, setNameValue] = useState(record?.name || ""); - let [descriptionValue, setDescriptionValue] = useState( - record?.description || "", - ); - let [iconFile, setIconFile] = useState(null); - let [iconPreview, setIconPreview] = useState(null); - let fileInputRef = useRef(null); - - let [showInDiscover, setShowInDiscover] = useState( - record?.preferences?.showInDiscover === undefined - ? true - : record.preferences.showInDiscover, - ); - - // --- Post Settings state --- - let [showComments, setShowComments] = useState( - record?.preferences?.showComments === undefined - ? true - : record.preferences.showComments, - ); - let [showMentions, setShowMentions] = useState( - record?.preferences?.showMentions === undefined - ? true - : record.preferences.showMentions, - ); - let [showRecommends, setShowRecommends] = useState( - record?.preferences?.showRecommends === undefined - ? true - : record.preferences.showRecommends, - ); - let [showPrevNext, setShowPrevNext] = useState( - record?.preferences?.showPrevNext === undefined - ? true - : record.preferences.showPrevNext, - ); - - // Sync from server data - useEffect(() => { - if (!pubData || !pubData.record || !record) return; - setNameValue(record.name); - setDescriptionValue(record.description || ""); - if (record.icon) - setIconPreview( - `/api/atproto_images?did=${pubData.identity_did}&cid=${(record.icon.ref as unknown as { $link: string })["$link"]}`, - ); - }, [pubData, record]); - - return ( -
{ - e.preventDefault(); - if (!pubData) return; - setLoading(true); - await updatePublication({ - uri: pubData.uri, - name: nameValue, - description: descriptionValue, - iconFile: iconFile, - preferences: { - showInDiscover, - showComments, - showMentions, - showPrevNext, - showRecommends, - }, - }); - toast({ type: "success", content: "Settings saved!" }); - setLoading(false); - mutate("publication-data"); - }} - > -
- {/* ── General Settings ── */} -
-

General Settings

- -
-

- Logo (optional) -

-
fileInputRef.current?.click()} - > - {iconPreview ? ( - Logo preview - ) : ( - - )} -
- { - const file = e.target.files?.[0]; - if (file) { - setIconFile(file); - const reader = new FileReader(); - reader.onload = (ev) => { - setIconPreview(ev.target?.result as string); - }; - reader.readAsDataURL(file); - } - }} - /> -
- - - - - - setShowInDiscover(!showInDiscover)} - > -
-

Show In Discover

-

- Your posts will appear in{" "} - - Leaflet Reader - - . You can change this at any time! -

-
-
-
- -
- - {/* ── Post Settings ── */} -
-

Post Settings

- -

Layout

- setShowPrevNext(!showPrevNext)} - > -
Show Prev/Next Buttons
-
- -
- -

Interactions

-
- setShowComments(!showComments)} - > -
Show Comments
-
- - setShowMentions(!showMentions)} - > -
-
Show Mentions
-
- Display a list of Bluesky mentions about your post -
-
-
- - setShowRecommends(!showRecommends)} - > -
-
Show Recommends
-
- Allow readers to recommend/like your post -
-
-
-
-
- -
- - {/* ── Theme ── */} -
-

Theme and Layout

- -
- -
- - {/* ── Domains ── */} - -
- - {/* ── Sticky Save Footer ── */} -
- - {loading ? : "Save Changes"} - -
-
- ); -} diff --git a/app/lish/[did]/[publication]/dashboard/settings/ThemeSettings.tsx b/app/lish/[did]/[publication]/dashboard/settings/ThemeSettings.tsx new file mode 100644 index 00000000..aa971823 --- /dev/null +++ b/app/lish/[did]/[publication]/dashboard/settings/ThemeSettings.tsx @@ -0,0 +1,14 @@ +export function ThemeSettings(props: { onOpenTheme: () => void }) { + return ( +
+

Theme and Layout

+ +
+ ); +} diff --git a/app/lish/createPub/CreatePubForm.tsx b/app/lish/createPub/CreatePubForm.tsx index c6138dac..a2f98267 100644 --- a/app/lish/createPub/CreatePubForm.tsx +++ b/app/lish/createPub/CreatePubForm.tsx @@ -149,13 +149,13 @@ export const CreatePubForm = () => { onChange={(e) => setShowInDiscover(e.target.checked)} >
-

Show In Discover

+

Make Public

Your posts will appear in{" "} Leaflet Reader - - . You can change this at any time! + {" "} + and show up in search and tags. You can change this at any time!

diff --git a/app/lish/createPub/UpdatePubForm.tsx b/app/lish/createPub/UpdatePubForm.tsx index 28464c4b..33251b87 100644 --- a/app/lish/createPub/UpdatePubForm.tsx +++ b/app/lish/createPub/UpdatePubForm.tsx @@ -165,13 +165,13 @@ export const EditPubForm = (props: { onToggle={() => setShowInDiscover(!showInDiscover)} >
-

Show In Discover

-

+

Make Public

+

Your posts will appear in{" "} Leaflet Reader - - . You can change this at any time! + {" "} + and show up in search and tags. You can change this at any time!

@@ -179,4 +179,3 @@ export const EditPubForm = (props: { ); }; - diff --git a/components/Icons/SettingsTiny.tsx b/components/Icons/SettingsTiny.tsx new file mode 100644 index 00000000..ee4f5d4b --- /dev/null +++ b/components/Icons/SettingsTiny.tsx @@ -0,0 +1,21 @@ +import { Props } from "./Props"; + +export const SettingsTiny = (props: Props) => { + return ( + + + + ); +}; diff --git a/components/PageLayouts/DashboardLayout.tsx b/components/PageLayouts/DashboardLayout.tsx index 9a28fcf7..d3e02f1b 100644 --- a/components/PageLayouts/DashboardLayout.tsx +++ b/components/PageLayouts/DashboardLayout.tsx @@ -132,6 +132,7 @@ export function DashboardLayout< [name: string]: { content: React.ReactNode; controls: React.ReactNode; + icon?: React.ReactNode; }; }, >(props: { @@ -206,6 +207,7 @@ export function DashboardLayout< setTabWithUrl(t)} onMouseEnter={() => props.onTabHover?.(t)} diff --git a/components/Tab.tsx b/components/Tab.tsx index dc66e2e9..9df04fa6 100644 --- a/components/Tab.tsx +++ b/components/Tab.tsx @@ -2,6 +2,7 @@ import { ExternalLinkTiny } from "./Icons/ExternalLinkTiny"; export const Tab = (props: { name: string; + icon?: React.ReactNode; selected: boolean; onSelect: () => void; onMouseEnter?: () => void; @@ -15,7 +16,7 @@ export const Tab = (props: { onMouseEnter={props.onMouseEnter} onPointerDown={props.onPointerDown} > - {props.name} + {props.icon ? props.icon : props.name} {props.href && } );