feat: crossbell renovation - support navigation
This commit is contained in:
parent
9a67856d6e
commit
3bed9051e5
|
|
@ -43,7 +43,7 @@ export const SitePage: React.FC<{
|
|||
contentHTML={page.body?.content || ""}
|
||||
className="my-10"
|
||||
/>
|
||||
<div className="bg-gray-100 rounded-lg py-8 px-5">
|
||||
<div className="bg-gray-100 rounded-lg py-8 px-5 break-all">
|
||||
<p className="text-gray-700 mb-4">🔔 This post has been permanently stored on-chain and signed by its creator.</p>
|
||||
<ul className="text-gray-600 text-sm leading-6">
|
||||
<li className="mt-2">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Note as UniNote } from "unidata.js"
|
||||
import type { Note as UniNote, Profile as UniProfile } from "unidata.js"
|
||||
|
||||
export type Site = {
|
||||
id: string
|
||||
|
|
@ -78,4 +78,13 @@ export type Note = UniNote & {
|
|||
export type Notes = {
|
||||
total: number,
|
||||
list: Note[],
|
||||
}
|
||||
|
||||
export type Profile = UniProfile & {
|
||||
navigation?: SiteNavigationItem[]
|
||||
}
|
||||
|
||||
export type Profiles = {
|
||||
total: number,
|
||||
list: Note[],
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { isUUID } from "~/lib/uuid"
|
|||
import { MembershipRole, PageType, type Site } from "~/lib/db.server"
|
||||
import { Gate } from "~/lib/gate.server"
|
||||
import { sendLoginEmail } from "~/lib/mailgun.server"
|
||||
import { SiteNavigationItem } from "~/lib/types"
|
||||
import { SiteNavigationItem, Profile } from "~/lib/types"
|
||||
import { nanoid } from "nanoid"
|
||||
import { getMembership } from "./membership"
|
||||
import { checkReservedWords } from "~/lib/reserved-words"
|
||||
|
|
@ -60,7 +60,11 @@ export const getSite = async (input: string) => {
|
|||
platform: 'Crossbell',
|
||||
});
|
||||
|
||||
const site = profiles.list?.sort((a, b) => +new Date(b.date_updated || 0) - +new Date(a.date_updated || 0))?.[0]
|
||||
const site: Profile = profiles.list?.sort((a, b) => +new Date(b.date_updated || 0) - +new Date(a.date_updated || 0))?.[0]
|
||||
const navigation = site.tags?.find(tag => tag.startsWith('navigation:'))?.replace('navigation:', "")
|
||||
if (navigation) {
|
||||
site.navigation = JSON.parse(navigation)
|
||||
}
|
||||
|
||||
if (!site) return null
|
||||
|
||||
|
|
@ -92,7 +96,7 @@ export async function updateSite(
|
|||
description?: string
|
||||
icon?: string | null
|
||||
subdomain?: string
|
||||
navigation?: SiteNavigationItem[] // TODO
|
||||
navigation?: SiteNavigationItem[]
|
||||
},
|
||||
) {
|
||||
return await unidata.profiles.set({
|
||||
|
|
@ -105,6 +109,7 @@ export async function updateSite(
|
|||
...(payload.description && { bio: payload.description }),
|
||||
...(payload.icon && { avatars: [payload.icon] }),
|
||||
...(payload.subdomain && { username: payload.subdomain }),
|
||||
...(payload.navigation && { tags: ['navigation:' + JSON.stringify(payload.navigation)] }),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
|||
import { useRouter } from "next/router"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { trpc } from "~/lib/trpc"
|
||||
import { SiteNavigationItem } from "~/lib/types"
|
||||
import { SiteNavigationItem, Profile } from "~/lib/types"
|
||||
import { nanoid } from "nanoid"
|
||||
import { ReactSortable } from "react-sortablejs"
|
||||
import equal from "fast-deep-equal"
|
||||
import { getSite, updateSite } from "~/models/site.model"
|
||||
|
||||
type UpdateItem = (id: string, newItem: Partial<SiteNavigationItem>) => void
|
||||
|
||||
|
|
@ -69,20 +70,23 @@ export default function SiteSettingsNavigationPage() {
|
|||
const router = useRouter()
|
||||
|
||||
const subdomain = router.query.subdomain as string
|
||||
const trpcContext = trpc.useContext()
|
||||
const siteResult = trpc.useQuery(["site", { site: subdomain }], {
|
||||
enabled: !!subdomain,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
const updateSite = trpc.useMutation("site.updateSite")
|
||||
let [site, setSite] = useState<Profile | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (subdomain) {
|
||||
getSite(subdomain).then((site) => {
|
||||
setSite(site)
|
||||
})
|
||||
}
|
||||
}, [subdomain])
|
||||
|
||||
const [items, setItems] = useState<SiteNavigationItem[]>([])
|
||||
|
||||
const itemsModified = useMemo(() => {
|
||||
if (!siteResult.data) return false
|
||||
return !equal(items, siteResult.data.navigation)
|
||||
}, [items, siteResult.data])
|
||||
if (!site) return false
|
||||
return !equal(items, site.navigation)
|
||||
}, [items, site])
|
||||
|
||||
const updateItem: UpdateItem = (id, newItem) => {
|
||||
setItems((items) => {
|
||||
|
|
@ -99,11 +103,20 @@ export default function SiteSettingsNavigationPage() {
|
|||
setItems((items) => [...items, { id: nanoid(), label: "", url: "" }])
|
||||
}
|
||||
|
||||
let [loading, setLoading] = useState<boolean>(false)
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
updateSite.mutate({
|
||||
site: siteResult.data!.id,
|
||||
setLoading(true)
|
||||
updateSite({
|
||||
site: site!.username!,
|
||||
navigation: items,
|
||||
}).then((result) => {
|
||||
if (result.code === 0) {
|
||||
toast.success("Saved")
|
||||
} else {
|
||||
toast.error("Failed to save" + ": " + result.message)
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -112,18 +125,10 @@ export default function SiteSettingsNavigationPage() {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (siteResult.data?.navigation) {
|
||||
setItems(siteResult.data.navigation)
|
||||
if (site?.navigation) {
|
||||
setItems(site.navigation)
|
||||
}
|
||||
}, [siteResult.data?.navigation])
|
||||
|
||||
useEffect(() => {
|
||||
if (updateSite.isSuccess) {
|
||||
updateSite.reset()
|
||||
toast.success("Saved")
|
||||
trpcContext.invalidateQueries(["site", { site: subdomain }])
|
||||
}
|
||||
}, [updateSite, trpcContext, subdomain])
|
||||
}, [site?.navigation])
|
||||
|
||||
return (
|
||||
<DashboardLayout title="Navigation">
|
||||
|
|
@ -135,7 +140,6 @@ export default function SiteSettingsNavigationPage() {
|
|||
No navigation items yet
|
||||
</div>
|
||||
)}
|
||||
{/** @ts-expect-error */}
|
||||
<ReactSortable
|
||||
list={items}
|
||||
setList={setItems}
|
||||
|
|
@ -161,7 +165,7 @@ export default function SiteSettingsNavigationPage() {
|
|||
<div className="border-t pt-5 mt-10 space-x-3 flex items-center">
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={updateSite.isLoading}
|
||||
isLoading={loading}
|
||||
isDisabled={!itemsModified}
|
||||
>
|
||||
Save
|
||||
|
|
|
|||
Loading…
Reference in New Issue