feat: use react query for site requests
This commit is contained in:
parent
2f0bc2427f
commit
074611e88d
|
|
@ -7,3 +7,5 @@ NEXT_PUBLIC_APP_NAME=Crosslog
|
|||
NEXT_PUBLIC_OUR_DOMAIN=localhost:3000
|
||||
NEXT_PUBLIC_DISCORD_LINK="https://discord.gg/9XscSqJEq4"
|
||||
NEXT_PUBLIC_GITHUB_LINK="https://github.com/Crossbell-Box/Crosslog"
|
||||
|
||||
CACHE_MAX_AGE=600000
|
||||
|
|
@ -25,6 +25,8 @@
|
|||
"@prisma/client": "^3.14.0",
|
||||
"@proselog/jwt": "^0.1.1",
|
||||
"@rainbow-me/rainbowkit": "^0.3.3",
|
||||
"@tanstack/react-query": "^4.0.10",
|
||||
"@tanstack/react-query-devtools": "^4.0.10",
|
||||
"@trpc/client": "^9.23.2",
|
||||
"@trpc/next": "^9.23.2",
|
||||
"@trpc/react": "^9.23.2",
|
||||
|
|
@ -51,7 +53,6 @@
|
|||
"react-dom": "^18.1.0",
|
||||
"react-hook-form": "^7.31.1",
|
||||
"react-hot-toast": "^2.2.0",
|
||||
"react-query": "^3.39.0",
|
||||
"react-sortablejs": "^6.1.1",
|
||||
"refractor": "^4.7.0",
|
||||
"rehype-prism-plus": "^1.4.0",
|
||||
|
|
@ -63,7 +64,7 @@
|
|||
"remark-rehype": "^10.1.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"unidata.js": "^0.6.2",
|
||||
"unidata.js": "^0.6.3",
|
||||
"unified": "^10.1.2",
|
||||
"unist-builder": "^3.0.0",
|
||||
"unist-util-visit": "^4.1.0",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ specifiers:
|
|||
'@prisma/client': ^3.14.0
|
||||
'@proselog/jwt': ^0.1.1
|
||||
'@rainbow-me/rainbowkit': ^0.3.3
|
||||
'@tanstack/react-query': ^4.0.10
|
||||
'@tanstack/react-query-devtools': ^4.0.10
|
||||
'@trpc/client': ^9.23.2
|
||||
'@trpc/next': ^9.23.2
|
||||
'@trpc/react': ^9.23.2
|
||||
|
|
@ -66,7 +68,6 @@ specifiers:
|
|||
react-dom: ^18.1.0
|
||||
react-hook-form: ^7.31.1
|
||||
react-hot-toast: ^2.2.0
|
||||
react-query: ^3.39.0
|
||||
react-sortablejs: ^6.1.1
|
||||
refractor: ^4.7.0
|
||||
rehype-prism-plus: ^1.4.0
|
||||
|
|
@ -80,7 +81,7 @@ specifiers:
|
|||
tailwindcss: ^3.0.24
|
||||
typescript: ^4.5.5
|
||||
ua-parser-js: ^1.0.2
|
||||
unidata.js: ^0.6.2
|
||||
unidata.js: ^0.6.3
|
||||
unified: ^10.1.2
|
||||
unist-builder: ^3.0.0
|
||||
unist-util-visit: ^4.1.0
|
||||
|
|
@ -104,9 +105,11 @@ dependencies:
|
|||
'@prisma/client': 3.15.2_prisma@3.15.2
|
||||
'@proselog/jwt': 0.1.1
|
||||
'@rainbow-me/rainbowkit': 0.3.4_ksvpodatviltilqty4m7gcxati
|
||||
'@tanstack/react-query': 4.0.10_biqbaboplfbrettd7655fr4n2y
|
||||
'@tanstack/react-query-devtools': 4.0.10_react@18.2.0
|
||||
'@trpc/client': 9.25.3_@trpc+server@9.25.3
|
||||
'@trpc/next': 9.25.3_uh6lro2uvcz55p6qikxrpfi5oa
|
||||
'@trpc/react': 9.25.3_ms3at4dqhndwrpx5xroirjehyy
|
||||
'@trpc/next': 9.25.3_wdrv5arzz3rco7suaqfslncqi4
|
||||
'@trpc/react': 9.25.3_5nj7bll4cny4mklqtydexm3u4e
|
||||
'@trpc/server': 9.25.3
|
||||
'@web-std/form-data': 3.0.2
|
||||
async-busboy: 1.1.0
|
||||
|
|
@ -130,7 +133,6 @@ dependencies:
|
|||
react-dom: 18.2.0_react@18.2.0
|
||||
react-hook-form: 7.33.0_react@18.2.0
|
||||
react-hot-toast: 2.2.0_biqbaboplfbrettd7655fr4n2y
|
||||
react-query: 3.39.1_biqbaboplfbrettd7655fr4n2y
|
||||
react-sortablejs: 6.1.4_yi7d4rnqh6snv6o2bvvwtjnph4
|
||||
refractor: 4.7.0
|
||||
rehype-prism-plus: 1.4.1
|
||||
|
|
@ -142,7 +144,7 @@ dependencies:
|
|||
remark-rehype: 10.1.0
|
||||
sortablejs: 1.15.0
|
||||
ua-parser-js: 1.0.2
|
||||
unidata.js: 0.6.2
|
||||
unidata.js: 0.6.3
|
||||
unified: 10.1.2
|
||||
unist-builder: 3.0.0
|
||||
unist-util-visit: 4.1.0
|
||||
|
|
@ -1367,6 +1369,46 @@ packages:
|
|||
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
||||
dev: true
|
||||
|
||||
/@tanstack/match-sorter-utils/8.1.1:
|
||||
resolution: {integrity: sha512-IdmEekEYxQsoLOR0XQyw3jD1GujBpRRYaGJYQUw1eOT1eUugWxdc7jomh1VQ1EKHcdwDLpLaCz/8y4KraU4T9A==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
remove-accents: 0.4.2
|
||||
dev: false
|
||||
|
||||
/@tanstack/query-core/4.0.10:
|
||||
resolution: {integrity: sha512-9LsABpZXkWZHi4P1ozRETEDXQocLAxVzQaIhganxbNuz/uA3PsCAJxJTiQrknG5htLMzOF5MqM9G10e6DCxV1A==}
|
||||
dev: false
|
||||
|
||||
/@tanstack/react-query-devtools/4.0.10_react@18.2.0:
|
||||
resolution: {integrity: sha512-3J7LLYQjfjTI0DbPo0bA+M3l4kdvYSWAqihpeG1u93WVyZj0OEFviUv+4cK7+k2AVgQJAPMZ5xvtewKxOOFVrw==}
|
||||
dependencies:
|
||||
'@tanstack/match-sorter-utils': 8.1.1
|
||||
'@types/use-sync-external-store': 0.0.3
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
dev: false
|
||||
|
||||
/@tanstack/react-query/4.0.10_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-Wn5QhZUE5wvr6rGClV7KeQIUsdTmYR9mgmMZen7DSRWauHW2UTynFg3Kkf6pw+XlxxOLsyLWwz/Q6q1lSpM3TQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@tanstack/query-core': 4.0.10
|
||||
'@types/use-sync-external-store': 0.0.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@trpc/client/9.25.3_@trpc+server@9.25.3:
|
||||
resolution: {integrity: sha512-4R+IK3cCDjoOTwHDyu+uN0bF3mYQI34i6aDNU3D/dJJxcA8PX5f8yb++nnR4RT6oTnu3yiR3PFC4KslvBtyi9w==}
|
||||
peerDependencies:
|
||||
|
|
@ -1376,7 +1418,7 @@ packages:
|
|||
'@trpc/server': 9.25.3
|
||||
dev: false
|
||||
|
||||
/@trpc/next/9.25.3_uh6lro2uvcz55p6qikxrpfi5oa:
|
||||
/@trpc/next/9.25.3_wdrv5arzz3rco7suaqfslncqi4:
|
||||
resolution: {integrity: sha512-eAhPIinDTUiEgiPjChNIcqaPgwn+rJ8HX374fwff/tZXg5OVr3dA7rur2nYyQUWO8LvdiLfUAbTxgtoEB8SPKw==}
|
||||
peerDependencies:
|
||||
'@trpc/client': 9.25.3
|
||||
|
|
@ -1389,16 +1431,15 @@ packages:
|
|||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@trpc/client': 9.25.3_@trpc+server@9.25.3
|
||||
'@trpc/react': 9.25.3_ms3at4dqhndwrpx5xroirjehyy
|
||||
'@trpc/react': 9.25.3_5nj7bll4cny4mklqtydexm3u4e
|
||||
'@trpc/server': 9.25.3
|
||||
next: 12.1.6_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-query: 3.39.1_biqbaboplfbrettd7655fr4n2y
|
||||
react-ssr-prepass: 1.5.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@trpc/react/9.25.3_ms3at4dqhndwrpx5xroirjehyy:
|
||||
/@trpc/react/9.25.3_5nj7bll4cny4mklqtydexm3u4e:
|
||||
resolution: {integrity: sha512-/StrJS7hKPPn88YaXX+qRU50MeLZxBmPC5BLVyhRtwmbzK25Q0MTzBgQLrjoCpJ/HgXYZEbB5ddiw8f+/WnqIA==}
|
||||
peerDependencies:
|
||||
'@trpc/client': 9.25.3
|
||||
|
|
@ -1412,7 +1453,6 @@ packages:
|
|||
'@trpc/server': 9.25.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-query: 3.39.1_biqbaboplfbrettd7655fr4n2y
|
||||
dev: false
|
||||
|
||||
/@trpc/server/9.25.3:
|
||||
|
|
@ -4081,7 +4121,7 @@ packages:
|
|||
resolution: {integrity: sha512-UHHu1HqW22ZhK6C/1Zmjf7mQpOwPwLYZ+xcsOgpzistONU8QqvCop6Od29p/kw1GUVoq2Ihu6ItpKLtlojx4FQ==}
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
undici: 5.6.0
|
||||
undici: 5.8.0
|
||||
dev: false
|
||||
|
||||
/file-entry-cache/6.0.1:
|
||||
|
|
@ -6779,25 +6819,6 @@ packages:
|
|||
p-defer: 3.0.0
|
||||
dev: false
|
||||
|
||||
/react-query/3.39.1_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-qYKT1bavdDiQZbngWZyPotlBVzcBjDYEJg5RQLBa++5Ix5jjfbEYJmHSZRZD+USVHUSvl/ey9Hu+QfF1QAK80A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
broadcast-channel: 3.7.0
|
||||
match-sorter: 6.3.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/react-query/4.0.0-beta.23_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-e6mNBVAYGy0M1OwX0mhRB/lCkOedKeqTUrbPjNCqvm8hQGUsJJobqfHVvTv8o6JJaOO2MFcxKF4vZM+PEKbHZA==}
|
||||
peerDependencies:
|
||||
|
|
@ -7713,13 +7734,13 @@ packages:
|
|||
engines: {node: '>=12.18'}
|
||||
dev: true
|
||||
|
||||
/undici/5.6.0:
|
||||
resolution: {integrity: sha512-mc+8SY1fXubTrdx4CXDkeFFGV8lI3Tq4I/70U1V8Z6g4iscGII0uLO7CPnDt56bXEbvaKwo2T2+VrteWbZiXiQ==}
|
||||
/undici/5.8.0:
|
||||
resolution: {integrity: sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q==}
|
||||
engines: {node: '>=12.18'}
|
||||
dev: false
|
||||
|
||||
/unidata.js/0.6.2:
|
||||
resolution: {integrity: sha512-5QYGPwrWPDDmjdkxzFD9H/402Cjx1m40+5IUx6h/eLMSCWCu8oXsjANmFE1M7ZK9Fd/9nMWXBhvAB3I6i0ErEA==}
|
||||
/unidata.js/0.6.3:
|
||||
resolution: {integrity: sha512-w2vWxsJGIZ+t2CLO9yxBStHbuMijmoaPKw4jBhfrUtVuUjD0MMGIUZ5mVbDbbvaNDKCWli5JqaNZjaSmCCmaYw==}
|
||||
dependencies:
|
||||
'@types/lodash': 4.14.182
|
||||
'@urql/core': 2.5.0_graphql@16.5.0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Dialog } from "@headlessui/react"
|
||||
import { useRef, useState } from "react"
|
||||
import { useRef, useState, useEffect } from "react"
|
||||
import ReactAvatarEditor from "react-avatar-editor"
|
||||
import { getUserContentsUrl } from "~/lib/user-contents"
|
||||
import { Avatar } from "~/components/ui/Avatar"
|
||||
|
|
@ -7,7 +7,7 @@ import { Button } from "~/components/ui/Button"
|
|||
import toast from "react-hot-toast"
|
||||
import createPica from "pica"
|
||||
import { UploadFile, useUploadFile } from "~/hooks/useUploadFile"
|
||||
import { updateSite } from "~/models/site.model"
|
||||
import { useUpdateSite } from "~/queries/site"
|
||||
|
||||
const AvatarEditorModal: React.FC<{
|
||||
isOpen: boolean
|
||||
|
|
@ -17,16 +17,13 @@ const AvatarEditorModal: React.FC<{
|
|||
uploadFile: UploadFile
|
||||
}> = ({ isOpen, setIsOpen, image, site, uploadFile }) => {
|
||||
const editorRef = useRef<ReactAvatarEditor | null>(null)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const updateSite = useUpdateSite()
|
||||
|
||||
const cropAndSave = async () => {
|
||||
if (!editorRef.current) return
|
||||
|
||||
try {
|
||||
setIsSaving(true)
|
||||
|
||||
// Get cropped image
|
||||
const fromCanvas = editorRef.current.getImage()
|
||||
const useCropAndSave = async () => {
|
||||
let key;
|
||||
// Get cropped image
|
||||
if (editorRef.current) {
|
||||
const fromCanvas = editorRef.current?.getImage()
|
||||
const toCanvas = document.createElement("canvas")
|
||||
toCanvas.width = 460
|
||||
toCanvas.height = 460
|
||||
|
|
@ -35,26 +32,22 @@ const AvatarEditorModal: React.FC<{
|
|||
const blob = await pica.toBlob(result, "image/jpeg", 0.9)
|
||||
|
||||
// Upload image to R2
|
||||
const { key } = await uploadFile(blob, image!.name)
|
||||
|
||||
// Save the image to profile / site
|
||||
const res = await updateSite({ site, icon: key })
|
||||
|
||||
if (res.code === 0) {
|
||||
setIsOpen(false)
|
||||
toast.success("Updated!")
|
||||
} else {
|
||||
toast.error("Failed to update site" + ": " + res.message)
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
toast.error(error.message)
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
key = (await uploadFile(blob, image!.name)).key
|
||||
}
|
||||
|
||||
// Save the image to profile / site
|
||||
updateSite.mutate({ site, icon: key })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (updateSite.isSuccess) {
|
||||
setIsOpen(false)
|
||||
toast.success("Updated!")
|
||||
} else if (updateSite.isError) {
|
||||
toast.error("Failed to update site")
|
||||
}
|
||||
}, [updateSite, setIsOpen])
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
|
|
@ -81,7 +74,7 @@ const AvatarEditorModal: React.FC<{
|
|||
</div>
|
||||
|
||||
<div className="h-16 border-t flex items-center px-5">
|
||||
<Button isBlock onClick={cropAndSave} isLoading={isSaving}>
|
||||
<Button isBlock onClick={useCropAndSave} isLoading={updateSite.isLoading}>
|
||||
Crop and Save
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { DashboardIcon } from "../icons/DashboardIcon"
|
|||
import { UniLink } from "../ui/UniLink"
|
||||
import { DashboardSidebar } from "./DashboardSidebar"
|
||||
import { SiteSwitcher } from "./SiteSwitcher"
|
||||
import { getUserSites } from "~/models/site.model"
|
||||
import { useGetUserSites } from "~/queries/site"
|
||||
import { useAccount } from 'wagmi'
|
||||
import type { Profile } from "unidata.js"
|
||||
|
||||
|
|
@ -25,14 +25,10 @@ export function DashboardLayout({
|
|||
|
||||
const { data: viewer } = useAccount()
|
||||
|
||||
let [subscriptions, setSubscriptions] = useState<Profile[]>([])
|
||||
const userSite = useGetUserSites(viewer?.address);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewer?.address) {
|
||||
getUserSites(viewer.address).then((sites) => {
|
||||
setSubscriptions(sites || [])
|
||||
})
|
||||
} else {
|
||||
if (!viewer?.address) {
|
||||
router.push("/")
|
||||
}
|
||||
}, [viewer?.address, router])
|
||||
|
|
@ -78,7 +74,7 @@ export function DashboardLayout({
|
|||
<div className="mb-2">
|
||||
<SiteSwitcher
|
||||
subdomain={subdomain}
|
||||
subscriptions={subscriptions}
|
||||
subscriptions={userSite.data || []}
|
||||
viewer={viewer}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useRouter } from "next/router"
|
|||
import { useStore } from "~/lib/store"
|
||||
|
||||
export type SiteLayoutProps = {
|
||||
site: Profile
|
||||
site?: Profile
|
||||
viewer?: Profile | null
|
||||
subscription?: { email?: boolean } | null
|
||||
children: React.ReactNode
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { PostMeta } from "./PostMeta"
|
|||
import { Profile, Note } from "~/lib/types"
|
||||
|
||||
export const SitePage: React.FC<{
|
||||
site: Profile,
|
||||
site?: Profile,
|
||||
page: Note
|
||||
}> = ({ site, page }) => {
|
||||
return (
|
||||
|
|
@ -20,9 +20,9 @@ export const SitePage: React.FC<{
|
|||
<PostMeta
|
||||
publishedAt={page.date_published}
|
||||
authors={[{
|
||||
id: site.username || "",
|
||||
name: site.name || "",
|
||||
avatar: site.avatars?.[0] || null,
|
||||
id: site?.username || "",
|
||||
name: site?.name || "",
|
||||
avatar: site?.avatars?.[0] || null,
|
||||
}] || []}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { Rendered, renderPageContent } from "~/markdown"
|
|||
import { notFound } from "~/lib/server-side-props"
|
||||
import { PageVisibilityEnum, Notes } from "~/lib/types"
|
||||
import { isUUID } from "~/lib/uuid"
|
||||
import { getSite } from "./site.model"
|
||||
import { stripHTML } from "~/lib/utils"
|
||||
import unidata from "~/lib/unidata"
|
||||
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ export const getSite = async (input: string) => {
|
|||
}
|
||||
site.name = site.name || site.username
|
||||
|
||||
if (!site) return null
|
||||
|
||||
return site
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import {
|
|||
WagmiConfig,
|
||||
} from 'wagmi'
|
||||
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'
|
||||
import { Hydrate, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
|
||||
import '@rainbow-me/rainbowkit/styles.css'
|
||||
|
||||
|
|
@ -50,13 +52,20 @@ const wagmiClient = createClient({
|
|||
provider
|
||||
})
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function MyApp({ Component, pageProps }: any) {
|
||||
return (
|
||||
<WagmiConfig client={wagmiClient}>
|
||||
<RainbowKitProvider chains={chains}>
|
||||
<StoreProvider createStore={createStore}>
|
||||
<Component {...pageProps} />
|
||||
<Toaster />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Hydrate state={pageProps.dehydratedState}>
|
||||
<ReactQueryDevtools />
|
||||
<Component {...pageProps} />
|
||||
<Toaster />
|
||||
</Hydrate>
|
||||
</QueryClientProvider>
|
||||
</StoreProvider>
|
||||
</RainbowKitProvider>
|
||||
</WagmiConfig>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import { SitePage } from "~/components/site/SitePage"
|
|||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { getViewer } from "~/lib/viewer"
|
||||
import { Viewer, Profile, Note } from "~/lib/types"
|
||||
import { getUserSites, getSite } from "~/models/site.model"
|
||||
import { queryClient, prefetchGetSite } from "~/queries/site.server"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query'
|
||||
import { getPage } from "~/models/page.model"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
import { notFound } from "~/lib/server-side-props"
|
||||
|
|
@ -16,9 +18,10 @@ export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
|||
async (ctx) => {
|
||||
const domainOrSubdomain = ctx.params!.site as string
|
||||
const pageSlug = ctx.params!.page as string
|
||||
|
||||
await prefetchGetSite(domainOrSubdomain)
|
||||
|
||||
const [site, page] = await Promise.all([
|
||||
getSite(domainOrSubdomain),
|
||||
const [page] = await Promise.all([
|
||||
getPage({
|
||||
site: domainOrSubdomain,
|
||||
page: pageSlug,
|
||||
|
|
@ -33,7 +36,7 @@ export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
|||
|
||||
return {
|
||||
props: {
|
||||
site,
|
||||
dehydratedState: dehydrate(queryClient),
|
||||
page,
|
||||
domainOrSubdomain,
|
||||
},
|
||||
|
|
@ -42,23 +45,22 @@ export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
|||
)
|
||||
|
||||
function SitePagePage({
|
||||
site,
|
||||
page,
|
||||
domainOrSubdomain,
|
||||
}: {
|
||||
site: Profile,
|
||||
page: Note,
|
||||
domainOrSubdomain: string
|
||||
}) {
|
||||
const ogDescription = page.summary?.content || page.body?.content
|
||||
const site = useGetSite(domainOrSubdomain)
|
||||
|
||||
return (
|
||||
<SiteLayout
|
||||
site={site!}
|
||||
site={site.data}
|
||||
title={page!.title}
|
||||
ogDescription={ogDescription}
|
||||
>
|
||||
<SitePage site={site!} page={page!} />
|
||||
<SitePage site={site.data} page={page!} />
|
||||
</SiteLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,19 @@ import { serverSidePropsHandler } from "~/lib/server-side-props"
|
|||
import { SiteArchives } from "~/components/site/SiteArchives"
|
||||
import { getViewer } from "~/lib/viewer"
|
||||
import { Viewer, Profile, Notes } from "~/lib/types"
|
||||
import { getSite } from "~/models/site.model"
|
||||
import { queryClient, prefetchGetSite } from "~/queries/site.server"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query'
|
||||
import { getPagesBySite } from "~/models/page.model"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const domainOrSubdomain = ctx.params!.site as string
|
||||
|
||||
await prefetchGetSite(domainOrSubdomain)
|
||||
|
||||
const [site, posts] = await Promise.all([
|
||||
getSite(domainOrSubdomain),
|
||||
const [posts] = await Promise.all([
|
||||
getPagesBySite({
|
||||
site: domainOrSubdomain,
|
||||
take: 1000,
|
||||
|
|
@ -27,7 +30,7 @@ export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
|||
|
||||
return {
|
||||
props: {
|
||||
site,
|
||||
dehydratedState: dehydrate(queryClient),
|
||||
posts,
|
||||
domainOrSubdomain,
|
||||
},
|
||||
|
|
@ -36,16 +39,18 @@ export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
|||
)
|
||||
|
||||
function SiteArchivesPage({
|
||||
site,
|
||||
posts,
|
||||
domainOrSubdomain,
|
||||
}: {
|
||||
site: Profile,
|
||||
posts: Notes,
|
||||
domainOrSubdomain: string
|
||||
}) {
|
||||
const site = useGetSite(domainOrSubdomain)
|
||||
|
||||
return (
|
||||
<SiteLayout
|
||||
site={site!}
|
||||
site={site.data}
|
||||
title="Archives"
|
||||
>
|
||||
<SiteArchives posts={posts} />
|
||||
|
|
|
|||
|
|
@ -7,15 +7,18 @@ import { Viewer, Profile, Notes } from "~/lib/types"
|
|||
import { getViewer } from "~/lib/viewer"
|
||||
import { useAccount } from 'wagmi'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getUserSites, getSite } from "~/models/site.model"
|
||||
import { queryClient, prefetchGetSite } from "~/queries/site.server"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query'
|
||||
import { getPagesBySite } from "~/models/page.model"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
const domainOrSubdomain = ctx.params!.site as string
|
||||
|
||||
const [site, posts] = await Promise.all([
|
||||
getSite(domainOrSubdomain),
|
||||
await prefetchGetSite(domainOrSubdomain)
|
||||
|
||||
const [posts] = await Promise.all([
|
||||
getPagesBySite({
|
||||
site: domainOrSubdomain,
|
||||
take: 1000,
|
||||
|
|
@ -26,7 +29,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|||
|
||||
return {
|
||||
props: {
|
||||
site,
|
||||
dehydratedState: dehydrate(queryClient),
|
||||
posts,
|
||||
domainOrSubdomain,
|
||||
},
|
||||
|
|
@ -34,15 +37,17 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|||
}
|
||||
|
||||
function SiteIndexPage({
|
||||
site,
|
||||
posts,
|
||||
domainOrSubdomain,
|
||||
}: {
|
||||
site: Profile,
|
||||
posts: Notes,
|
||||
domainOrSubdomain: string
|
||||
}) {
|
||||
const site = useGetSite(domainOrSubdomain)
|
||||
|
||||
return (
|
||||
<SiteLayout site={site!}>
|
||||
<SiteLayout site={site.data}>
|
||||
<SiteHome posts={posts} />
|
||||
</SiteLayout>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,22 +7,14 @@ import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
|||
import { Button } from "~/components/ui/Button"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { OUR_DOMAIN } from "~/lib/env"
|
||||
import { getSite, updateSite } from "~/models/site.model"
|
||||
import { Profile } from "unidata.js"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
export default function SettingsDomainsPage() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
|
||||
let [site, setSite] = useState<Profile | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (subdomain) {
|
||||
getSite(subdomain).then((site) => {
|
||||
setSite(site)
|
||||
})
|
||||
}
|
||||
}, [subdomain])
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
|
|
@ -30,28 +22,35 @@ export default function SettingsDomainsPage() {
|
|||
},
|
||||
})
|
||||
|
||||
let [loading, setLoading] = useState<boolean>(false)
|
||||
const handleSubmit = form.handleSubmit((values) => {
|
||||
setLoading(true)
|
||||
updateSite({
|
||||
updateSite.mutate({
|
||||
site: subdomain,
|
||||
subdomain: values.subdomain,
|
||||
})
|
||||
updateSite.mutate({
|
||||
site: subdomain,
|
||||
subdomain: values.subdomain,
|
||||
}).then((result) => {
|
||||
if (result.code === 0) {
|
||||
toast.success("Saved!")
|
||||
router.replace(
|
||||
`/dashboard/${values.subdomain}/settings/domains`
|
||||
)
|
||||
} else {
|
||||
toast.error("Failed to update site" + ": " + result.message)
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (site) {
|
||||
form.setValue("subdomain", site.username || "")
|
||||
if (updateSite.isSuccess) {
|
||||
if (updateSite.data?.code === 0) {
|
||||
toast.success("Saved!")
|
||||
router.replace(
|
||||
`/dashboard/${updateSite.variables?.subdomain}/settings/domains`
|
||||
)
|
||||
} else {
|
||||
toast.error("Failed to update site" + ": " + updateSite.data.message)
|
||||
}
|
||||
} else if (updateSite.isError) {
|
||||
toast.error("Failed to update site")
|
||||
}
|
||||
}, [updateSite, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (site.isSuccess && site.data) {
|
||||
form.setValue("subdomain", site.data.username || "")
|
||||
}
|
||||
}, [form, site])
|
||||
|
||||
|
|
@ -70,7 +69,7 @@ export default function SettingsDomainsPage() {
|
|||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Button type="submit" isLoading={loading}>
|
||||
<Button type="submit" isLoading={updateSite.isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,23 +7,15 @@ import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
|||
import { useRouter } from "next/router"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { getSite, updateSite } from "~/models/site.model"
|
||||
import { Profile } from "unidata.js"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
export default function SiteSettingsGeneralPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const subdomain = router.query.subdomain as string
|
||||
|
||||
let [site, setSite] = useState<Profile | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (subdomain) {
|
||||
getSite(subdomain).then((site) => {
|
||||
setSite(site)
|
||||
})
|
||||
}
|
||||
}, [subdomain])
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
|
|
@ -32,29 +24,32 @@ export default function SiteSettingsGeneralPage() {
|
|||
},
|
||||
})
|
||||
|
||||
let [loading, setLoading] = useState<boolean>(false)
|
||||
const handleSubmit = form.handleSubmit((values) => {
|
||||
setLoading(true)
|
||||
updateSite({
|
||||
updateSite.mutate({
|
||||
site: subdomain,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
}).then((result) => {
|
||||
if (result.code === 0) {
|
||||
toast.success("Site updated")
|
||||
} else {
|
||||
toast.error("Failed to update site" + ": " + result.message)
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (site) {
|
||||
form.setValue("name", site.name || "")
|
||||
form.setValue("description", site.bio || "")
|
||||
if (updateSite.isSuccess) {
|
||||
if (updateSite.data?.code === 0) {
|
||||
toast.success("Site updated")
|
||||
} else {
|
||||
toast.error("Failed to update site" + ": " + updateSite.data.message)
|
||||
}
|
||||
} else if (updateSite.isError) {
|
||||
toast.error("Failed to update site")
|
||||
}
|
||||
}, [site, form])
|
||||
}, [updateSite, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (site.data) {
|
||||
form.setValue("name", site.data.name || "")
|
||||
form.setValue("description", site.data.bio || "")
|
||||
}
|
||||
}, [site.data, form])
|
||||
|
||||
return (
|
||||
<DashboardLayout title="Site Settings">
|
||||
|
|
@ -63,9 +58,9 @@ export default function SiteSettingsGeneralPage() {
|
|||
<div>
|
||||
<label className="form-label">Icon</label>
|
||||
<AvatarForm
|
||||
site={site.username!}
|
||||
name={site.name || site.username || ""}
|
||||
filename={site.avatars?.[0] || undefined}
|
||||
site={site.data?.username!}
|
||||
name={site.data?.name || site.data?.username || ""}
|
||||
filename={site.data?.avatars?.[0] || undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -85,7 +80,7 @@ export default function SiteSettingsGeneralPage() {
|
|||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Button type="submit" isLoading={loading}>
|
||||
<Button type="submit" isLoading={updateSite.isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
type UpdateItem = (id: string, newItem: Partial<SiteNavigationItem>) => void
|
||||
|
||||
|
|
@ -70,21 +70,14 @@ export default function SiteSettingsNavigationPage() {
|
|||
|
||||
const subdomain = router.query.subdomain as string
|
||||
|
||||
let [site, setSite] = useState<Profile | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (subdomain) {
|
||||
getSite(subdomain).then((site) => {
|
||||
setSite(site)
|
||||
})
|
||||
}
|
||||
}, [subdomain])
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
const [items, setItems] = useState<SiteNavigationItem[]>([])
|
||||
|
||||
const itemsModified = useMemo(() => {
|
||||
if (!site) return false
|
||||
return !equal(items, site.navigation)
|
||||
if (!site.isSuccess) return false
|
||||
return !equal(items, site.data.navigation)
|
||||
}, [items, site])
|
||||
|
||||
const updateItem: UpdateItem = (id, newItem) => {
|
||||
|
|
@ -102,32 +95,35 @@ export default function SiteSettingsNavigationPage() {
|
|||
setItems((items) => [...items, { id: nanoid(), label: "", url: "" }])
|
||||
}
|
||||
|
||||
let [loading, setLoading] = useState<boolean>(false)
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
updateSite({
|
||||
site: site!.username!,
|
||||
updateSite.mutate({
|
||||
site: site.data?.username!,
|
||||
navigation: items,
|
||||
}).then((result) => {
|
||||
if (result.code === 0) {
|
||||
toast.success("Saved")
|
||||
} else {
|
||||
toast.error("Failed to save" + ": " + result.message)
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (updateSite.isSuccess) {
|
||||
if (updateSite.data?.code === 0) {
|
||||
toast.success("Saved")
|
||||
} else {
|
||||
toast.error("Failed to save" + ": " + updateSite.data.message)
|
||||
}
|
||||
} else if (updateSite.isError) {
|
||||
toast.error("Failed to save")
|
||||
}
|
||||
}, [updateSite, router])
|
||||
|
||||
const removeItem: RemoveItem = (id) => {
|
||||
setItems((items) => items.filter((item) => item.id !== id))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (site?.navigation) {
|
||||
setItems(site.navigation)
|
||||
if (site.data?.navigation) {
|
||||
setItems(site.data?.navigation)
|
||||
}
|
||||
}, [site?.navigation])
|
||||
}, [site.data?.navigation])
|
||||
|
||||
return (
|
||||
<DashboardLayout title="Navigation">
|
||||
|
|
@ -164,7 +160,7 @@ export default function SiteSettingsNavigationPage() {
|
|||
<div className="border-t pt-5 mt-10 space-x-3 flex items-center">
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={loading}
|
||||
isLoading={updateSite.isLoading}
|
||||
isDisabled={!itemsModified}
|
||||
>
|
||||
Save
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { redirect } from "~/lib/server-side-props"
|
||||
import { getUserSites } from "~/models/site.model"
|
||||
import { useGetUserSites } from "~/queries/site"
|
||||
import { useAccount } from 'wagmi'
|
||||
import { useEffect } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
|
|
@ -7,20 +7,17 @@ import { useRouter } from 'next/router'
|
|||
export default function Dashboard() {
|
||||
const { data: wagmiData } = useAccount()
|
||||
const router = useRouter()
|
||||
const userSites = useGetUserSites(wagmiData?.address)
|
||||
|
||||
useEffect(() => {
|
||||
if (wagmiData?.address) {
|
||||
getUserSites(wagmiData.address).then((sites) => {
|
||||
if (!sites) {
|
||||
router.push(`/dashboard/new-site`)
|
||||
} else {
|
||||
router.push(`/dashboard/${sites[0].username}`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
router.push("/")
|
||||
if (userSites.isSuccess) {
|
||||
if (!userSites.data?.length) {
|
||||
router.push(`/dashboard/new-site`)
|
||||
} else {
|
||||
router.push(`/dashboard/${userSites.data[0].username}`)
|
||||
}
|
||||
}
|
||||
}, [wagmiData?.address, router])
|
||||
}, [userSites, router])
|
||||
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ import { Button } from "~/components/ui/Button"
|
|||
import { Input } from "~/components/ui/Input"
|
||||
import { APP_NAME, OUR_DOMAIN } from "~/lib/env"
|
||||
import { useAccount } from "wagmi"
|
||||
import { createSite } from "~/models/site.model"
|
||||
import { useCreateSite } from "~/queries/site"
|
||||
|
||||
export default function NewSitePage() {
|
||||
const router = useRouter()
|
||||
const createSite = useCreateSite()
|
||||
|
||||
const [address, setAddress] = useState<string>('')
|
||||
const { data: wagmiData } = useAccount()
|
||||
|
|
@ -31,18 +32,25 @@ export default function NewSitePage() {
|
|||
},
|
||||
})
|
||||
|
||||
let [loading, setLoading] = useState<boolean>(false)
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
setLoading(true)
|
||||
const result = await createSite(wagmiData!.address!, values)
|
||||
setLoading(false)
|
||||
if (result.code === 0) {
|
||||
router.push(`/dashboard/${values.subdomain}`)
|
||||
} else {
|
||||
toast.error("Failed to create site" + ": " + result.message)
|
||||
}
|
||||
createSite.mutate({
|
||||
address: wagmiData!.address!,
|
||||
payload: values,
|
||||
})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (createSite.isSuccess) {
|
||||
if (createSite.data?.code === 0) {
|
||||
router.push(`/dashboard/${createSite.variables?.payload.subdomain}`)
|
||||
} else {
|
||||
toast.error("Failed to create site" + ": " + createSite.data.message)
|
||||
}
|
||||
} else if (createSite.isError) {
|
||||
toast.error("Failed to create site")
|
||||
}
|
||||
}, [createSite, router])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEOHead title="New Site" siteName={APP_NAME} />
|
||||
|
|
@ -96,7 +104,7 @@ export default function NewSitePage() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button type="submit" isBlock isLoading={loading}>
|
||||
<Button type="submit" isBlock isLoading={createSite.isLoading}>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import * as siteModel from "~/models/site.model"
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
|
||||
const queryClientServer = new QueryClient()
|
||||
const cacheMaxAge = parseInt(process.env.CACHE_MAX_AGE || "600000")
|
||||
|
||||
export const queryClient = queryClientServer
|
||||
|
||||
export const prefetchGetSite = async (input: string) => {
|
||||
await queryClientServer.prefetchQuery(['getSite', input], async () => {
|
||||
return siteModel.getSite(input)
|
||||
}, { staleTime: cacheMaxAge })
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import * as siteModel from "~/models/site.model"
|
||||
import { SiteNavigationItem, Profile } from "~/lib/types"
|
||||
|
||||
export const useGetUserSites = (address?: string) => {
|
||||
return useQuery(['getUserSites', address], async () => {
|
||||
if (!address) {
|
||||
return []
|
||||
}
|
||||
return siteModel.getUserSites(address)
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetSite = (input: string) => {
|
||||
return useQuery(['getSite', input], async () => {
|
||||
return siteModel.getSite(input)
|
||||
})
|
||||
}
|
||||
|
||||
export function useUpdateSite() {
|
||||
const queryClient = useQueryClient();
|
||||
const mutation = useMutation(async (
|
||||
payload: {
|
||||
site: string
|
||||
name?: string
|
||||
description?: string
|
||||
icon?: string | null
|
||||
subdomain?: string
|
||||
navigation?: SiteNavigationItem[]
|
||||
}) => {
|
||||
return siteModel.updateSite(payload)
|
||||
}, {
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries(['getUserSites'])
|
||||
queryClient.invalidateQueries(['getSite'])
|
||||
},
|
||||
})
|
||||
return mutation
|
||||
}
|
||||
|
||||
export function useCreateSite() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(async (input: {
|
||||
address: string,
|
||||
payload: { name: string; subdomain: string },
|
||||
}) => {
|
||||
return siteModel.createSite(input.address, input.payload)
|
||||
}, {
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries(['getUserSites', variables.address])
|
||||
},
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue