chore: clean (#1566)

* chore: run eslint fix

* chore: run prettier

* chore: run knip

* chore: update

* chore: update
This commit is contained in:
Stephen Zhou 2024-03-14 18:02:34 +08:00 committed by GitHub
parent 866a8539f2
commit 915c4aea76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 287 additions and 1135 deletions

View File

@ -34,4 +34,4 @@ We will provide some small gifts to contributors as a token of appreciation, cur
- [xLog custom css debugger](https://github.com/birdgg/xLog-custom-css-debugger): A convenient chrome extension for debugging custom CSS in Xlog. - [xLog custom css debugger](https://github.com/birdgg/xLog-custom-css-debugger): A convenient chrome extension for debugging custom CSS in Xlog.
- [xLog card](https://github.com/Alex-Programer/xlog-card): Card component for displaying xLog information. - [xLog card](https://github.com/Alex-Programer/xlog-card): Card component for displaying xLog information.
- [Sync To xLog](https://github.com/Otto-J/sync-to-xlog): Sync obsidian notes to xlog.app - [Sync To xLog](https://github.com/Otto-J/sync-to-xlog): Sync obsidian notes to xlog.app
- [Nyaruko Telegram Bot](https://github.com/niracler/nyaruko-telegram-bot): Synchronize Telegram messages to xLog as Shorts - [Nyaruko Telegram Bot](https://github.com/niracler/nyaruko-telegram-bot): Synchronize Telegram messages to xLog as Shorts

View File

@ -47,9 +47,6 @@
"@crossbell/util-hooks": "1.5.26", "@crossbell/util-hooks": "1.5.26",
"@crossbell/util-metadata": "1.5.26", "@crossbell/util-metadata": "1.5.26",
"@ddietr/codemirror-themes": "1.4.2", "@ddietr/codemirror-themes": "1.4.2",
"@dqbd/tiktoken": "1.0.13",
"@emoji-mart/data": "1.1.2",
"@emoji-mart/react": "1.1.1",
"@floating-ui/react": "0.26.9", "@floating-ui/react": "0.26.9",
"@headlessui/react": "1.7.18", "@headlessui/react": "1.7.18",
"@heroicons/react": "2.1.1", "@heroicons/react": "2.1.1",
@ -63,7 +60,6 @@
"@prisma/client": "5.9.1", "@prisma/client": "5.9.1",
"@supercharge/request-ip": "1.2.0", "@supercharge/request-ip": "1.2.0",
"@tanstack/react-query": "4.36.1", "@tanstack/react-query": "4.36.1",
"@tanstack/react-query-devtools": "4.36.1",
"@uiw/codemirror-extensions-events": "4.21.22", "@uiw/codemirror-extensions-events": "4.21.22",
"@urql/core": "4.2.3", "@urql/core": "4.2.3",
"@vercel/otel": "1.3.0", "@vercel/otel": "1.3.0",
@ -111,7 +107,6 @@
"path-to-regexp": "6.2.1", "path-to-regexp": "6.2.1",
"pinyin-pro": "3.19.5", "pinyin-pro": "3.19.5",
"plaiceholder": "3.0.0", "plaiceholder": "3.0.0",
"prismjs": "1.29.0",
"qier-progress": "1.0.4", "qier-progress": "1.0.4",
"qrcode.react": "3.1.0", "qrcode.react": "3.1.0",
"rc-dplayer": "1.0.0", "rc-dplayer": "1.0.0",
@ -143,7 +138,6 @@
"remark-math": "6.0.0", "remark-math": "6.0.0",
"remark-parse": "11.0.0", "remark-parse": "11.0.0",
"remark-rehype": "11.1.0", "remark-rehype": "11.1.0",
"remark-stringify": "11.0.0",
"remove-markdown": "0.5.0", "remove-markdown": "0.5.0",
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"serialize-javascript": "6.0.2", "serialize-javascript": "6.0.2",
@ -156,7 +150,6 @@
"unified": "11.0.4", "unified": "11.0.4",
"unist-builder": "4.0.0", "unist-builder": "4.0.0",
"unist-util-visit": "5.0.0", "unist-util-visit": "5.0.0",
"uuid": "9.0.1",
"vfile": "6.0.1", "vfile": "6.0.1",
"viem": "1.21.1", "viem": "1.21.1",
"wagmi": "1.4.13", "wagmi": "1.4.13",
@ -170,28 +163,19 @@
"@types/async-lock": "1.4.2", "@types/async-lock": "1.4.2",
"@types/canvas-confetti": "1.6.4", "@types/canvas-confetti": "1.6.4",
"@types/chroma-js": "2.4.4", "@types/chroma-js": "2.4.4",
"@types/cookie": "0.6.0",
"@types/hast": "3.0.4", "@types/hast": "3.0.4",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/katex": "0.16.7", "@types/katex": "0.16.7",
"@types/mdast": "4.0.3", "@types/mdast": "4.0.3",
"@types/mjml": "4.7.4",
"@types/node": "20.11.19", "@types/node": "20.11.19",
"@types/pangu": "4.0.2", "@types/pangu": "4.0.2",
"@types/prismjs": "1.26.3",
"@types/react": "18.2.56", "@types/react": "18.2.56",
"@types/react-dom": "18.2.19", "@types/react-dom": "18.2.19",
"@types/react-infinite-scroller": "1.2.5", "@types/remove-markdown": "^0.3.4",
"@types/remove-markdown": "0.3.4",
"@types/serialize-javascript": "5.0.4", "@types/serialize-javascript": "5.0.4",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
"@types/ua-parser-js": "0.7.39",
"@types/uuid": "9.0.8",
"autoprefixer": "10.4.17", "autoprefixer": "10.4.17",
"babel-loader": "9.1.3",
"cspell": "8.4.0", "cspell": "8.4.0",
"esbuild": "0.20.1",
"esbuild-register": "3.5.0",
"eslint": "8.56.0", "eslint": "8.56.0",
"eslint-config-next": "14.1.0", "eslint-config-next": "14.1.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@ export default async function HomeLayout({
href="/" href="/"
className="text-2xl font-extrabold flex items-center" className="text-2xl font-extrabold flex items-center"
> >
<div className="inline-block w-9 h-9 mr-3"> <div className="inline-block size-9 mr-3">
<Logo type="svg" width={36} height={36} autoplay={false} /> <Logo type="svg" width={36} height={36} autoplay={false} />
</div> </div>
xLog xLog

View File

@ -18,7 +18,7 @@ import { useGetSite } from "~/queries/site"
function SiteAvatar({ siteId }: { siteId: string }) { function SiteAvatar({ siteId }: { siteId: string }) {
const site = useGetSite(siteId) const site = useGetSite(siteId)
return ( return (
<div className="inline-block w-10 h-10 relative"> <div className="inline-block size-10 relative">
<CharacterFloatCard siteId={siteId}> <CharacterFloatCard siteId={siteId}>
<UniLink <UniLink
href={getSiteLink({ href={getSiteLink({
@ -91,7 +91,7 @@ export default function EventsPage() {
> >
{isUnread && ( {isUnread && (
<div> <div>
<span className="absolute -left-2 -top-2 bg-red-500 rounded-full w-4 h-4"></span> <span className="absolute -left-2 -top-2 bg-red-500 rounded-full size-4"></span>
</div> </div>
)} )}
<div className="flex flex-col p-7 justify-between h-full"> <div className="flex flex-col p-7 justify-between h-full">

View File

@ -33,7 +33,7 @@ export default function ImportPage() {
key={option.name} key={option.name}
href={pathname + option.path} href={pathname + option.path}
> >
<span className="w-8 h-8 mr-4"> <span className="size-8 mr-4">
<Image <Image
fill fill
src={`/assets/${option.icon}`} src={`/assets/${option.icon}`}

View File

@ -123,7 +123,7 @@ export default function SiteSettingsGeneralPage() {
control={form.control} control={form.control}
render={({ field }) => ( render={({ field }) => (
<ImageUploader <ImageUploader
className="w-24 h-24 rounded-full" className="size-24 rounded-full"
uploadStart={() => { uploadStart={() => {
setIconUploading(true) setIconUploading(true)
}} }}

View File

@ -40,7 +40,7 @@ function OIAPage() {
<div className="relative h-screen w-screen bg-accent max-w-4xl m-auto"> <div className="relative h-screen w-screen bg-accent max-w-4xl m-auto">
<Image <Image
fill fill
className="object-cover absolute h-full w-full" className="object-cover absolute size-full"
src="/assets/download-preview.png" src="/assets/download-preview.png"
alt="Download Preview" alt="Download Preview"
/> />

View File

@ -63,7 +63,7 @@ export default async function SiteNFTPage({
<div className="w-full aspect-[1] relative rounded overflow-hidden"> <div className="w-full aspect-[1] relative rounded overflow-hidden">
<div className="absolute inset-0"> <div className="absolute inset-0">
<Image <Image
className="object-cover no-optimization w-full h-full rounded-2xl" className="object-cover no-optimization size-full rounded-2xl"
alt={"nft"} alt={"nft"}
src={nft.previews.image_medium_url} src={nft.previews.image_medium_url}
fill={true} fill={true}

View File

@ -92,7 +92,7 @@ export const AchievementModal = ({
} }
> >
<Tilt <Tilt
className={`inline-block w-80 h-80 relative rounded-full bg-white mb-4 preserve-3d shadow-[inset_#a8a29e_34px_-34px_74px] p-[4%] ${ className={`inline-block size-80 relative rounded-full bg-white mb-4 preserve-3d shadow-[inset_#a8a29e_34px_-34px_74px] p-[4%] ${
!achievement && "grayscale" !achievement && "grayscale"
}`} }`}
trackOnWindow={true} trackOnWindow={true}
@ -110,7 +110,7 @@ export const AchievementModal = ({
(achievement || achievementMintable || achievementComing)!.info (achievement || achievementMintable || achievementComing)!.info
.media .media
} }
className="relative w-full h-full" className="relative size-full"
style={{ style={{
transform: "translateZ(20px)", transform: "translateZ(20px)",
}} }}

View File

@ -149,9 +149,9 @@ export const ConnectButton = ({
<> <>
{t("Operator Sign")} ( {t("Operator Sign")} (
{isOpSignEnabled ? ( {isOpSignEnabled ? (
<FaceSmileIcon className="w-4 h-4" /> <FaceSmileIcon className="size-4" />
) : ( ) : (
<FaceFrownIcon className="w-4 h-4" /> <FaceFrownIcon className="size-4" />
)} )}
) )
</> </>
@ -253,14 +253,14 @@ export const ConnectButton = ({
{isAllRead ? ( {isAllRead ? (
<BellIcon <BellIcon
className={`${ className={`${
size === "base" ? "w-6 h-6" : "w-5 h-5" size === "base" ? "size-6" : "size-5"
} text-zinc-500 cursor-pointer sm:hover:animate-buzz-out`} } text-zinc-500 cursor-pointer sm:hover:animate-buzz-out`}
onClick={showNotificationModal} onClick={showNotificationModal}
/> />
) : ( ) : (
<BellAlertIcon <BellAlertIcon
className={`${ className={`${
size === "base" ? "w-6 h-6" : "w-5 h-5" size === "base" ? "size-6" : "size-5"
} text-accent cursor-pointer sm:hover:animate-buzz-out`} } text-accent cursor-pointer sm:hover:animate-buzz-out`}
onClick={showNotificationModal} onClick={showNotificationModal}
/> />
@ -270,7 +270,7 @@ export const ConnectButton = ({
target={ target={
<PlusCircleIcon <PlusCircleIcon
className={`${ className={`${
size === "base" ? "w-6 h-6 ml-2" : "w-5 h-5 ml-1" size === "base" ? "size-6 ml-2" : "size-5 ml-1"
} text-zinc-500 cursor-pointer hidden sm:block`} } text-zinc-500 cursor-pointer hidden sm:block`}
/> />
} }
@ -304,7 +304,7 @@ export const ConnectButton = ({
} }
/> />
<div className="h-full w-[2px] py-1 ml-2"> <div className="h-full w-[2px] py-1 ml-2">
<div className="w-full h-full bg-zinc-200 rounded-full"></div> <div className="size-full bg-zinc-200 rounded-full"></div>
</div> </div>
</> </>
)} )}

View File

@ -64,7 +64,7 @@ export const Logo = ({
autoplay={autoplay ?? true} autoplay={autoplay ?? true}
lottieRef={ref} lottieRef={ref}
onMouseEnter={() => ref.current?.goToAndPlay(0)} onMouseEnter={() => ref.current?.goToAndPlay(0)}
className="xlog-lottie-logo text-black w-full h-full" className="xlog-lottie-logo text-black size-full"
/> />
)} )}
</div> </div>

View File

@ -35,7 +35,7 @@ export const usePatronModal = () => {
present({ present({
title: ( title: (
<span className="inline-flex items-center justify-center w-full space-x-1"> <span className="inline-flex items-center justify-center w-full space-x-1">
<span className="text-red-500 flex w-6 h-6 mb-[-1px]"> <span className="text-red-500 flex size-6 mb-[-1px]">
<i className="i-mingcute-heart-fill text-2xl mb-[-1px]" /> <i className="i-mingcute-heart-fill text-2xl mb-[-1px]" />
</span> </span>
<span className="truncate">{title}</span> <span className="truncate">{title}</span>
@ -220,7 +220,7 @@ const PatronModal = ({
))} ))}
{tips.data.pages?.[0]?.count > 7 && ( {tips.data.pages?.[0]?.count > 7 && (
<li className="inline-flex justify-center w-[12.5%] h-[70px]"> <li className="inline-flex justify-center w-[12.5%] h-[70px]">
<div className="relative align-middle w-[50px] h-[50px] rounded-full inline-flex bg-gray-100 items-center justify-center text-gray-400 font-medium"> <div className="relative align-middle size-[50px] rounded-full inline-flex bg-gray-100 items-center justify-center text-gray-400 font-medium">
+{tips.data.pages?.[0]?.count - 7} +{tips.data.pages?.[0]?.count - 7}
</div> </div>
</li> </li>

View File

@ -171,7 +171,7 @@ const ShareModal: FC<ShareModalProps> = ({ url, text, title }) => {
{shareList.map(({ name, icon, onClick }) => ( {shareList.map(({ name, icon, onClick }) => (
<li <li
key={name} key={name}
className="flex cursor-pointer items-center space-x-2 rounded-md px-3 py-2 text-lg transition-colors hover:bg-gray-100 [&_img]:w-[1rem] [&_img]:h-[1rem]" className="flex cursor-pointer items-center space-x-2 rounded-md px-3 py-2 text-lg transition-colors hover:bg-gray-100 [&_img]:size-[1rem]"
aria-label={`Share to ${name}`} aria-label={`Share to ${name}`}
role="button" role="button"
onClick={() => onClick({ url, text, title })} onClick={() => onClick({ url, text, title })}

View File

@ -49,7 +49,7 @@ const ToolbarItemWithPopover = ({
> >
<Popover.Button <Popover.Button
key={name} key={name}
className="w-9 h-9 transition-colors text-lg border border-transparent rounded flex items-center justify-center text-zinc-500 group-hover:text-zinc-600 hover:text-zinc-500 hover:border-zinc-300 hover:bg-zinc-100" className="size-9 transition-colors text-lg border border-transparent rounded flex items-center justify-center text-zinc-500 group-hover:text-zinc-600 hover:text-zinc-500 hover:border-zinc-300 hover:bg-zinc-100"
ref={setPopoverButtonRef} ref={setPopoverButtonRef}
> >
<span className={icon}></span> <span className={icon}></span>
@ -107,7 +107,7 @@ export const EditorToolbar = memo(({ view }: { view?: EditorView }) => {
title={name} title={name}
type="button" type="button"
className={ className={
"w-9 h-9 transition-colors text-lg border border-transparent rounded flex items-center justify-center text-zinc-500 group-hover:text-zinc-600 hover:text-zinc-500 hover:border-zinc-300 hover:bg-zinc-100" "size-9 transition-colors text-lg border border-transparent rounded flex items-center justify-center text-zinc-500 group-hover:text-zinc-600 hover:text-zinc-500 hover:border-zinc-300 hover:bg-zinc-100"
} }
onClick={() => { onClick={() => {
view && view &&

View File

@ -40,13 +40,13 @@ export const OptionsButton = ({
return ( return (
<> <>
<div <div
className="bg-accent rounded-full cursor-pointer text-white w-6 h-6 flex justify-center items-center" className="bg-accent rounded-full cursor-pointer text-white size-6 flex justify-center items-center"
onClick={() => renderPage(!isRendering)} onClick={() => renderPage(!isRendering)}
> >
{isRendering ? ( {isRendering ? (
<i className="i-mingcute-eye-close-line text-xl inline-block w-5 h-5" /> <i className="i-mingcute-eye-close-line text-xl inline-block size-5" />
) : ( ) : (
<i className="i-mingcute-eye-2-line text-xl inline-block w-5 h-5" /> <i className="i-mingcute-eye-2-line text-xl inline-block size-5" />
)} )}
</div> </div>
<Popover className="relative"> <Popover className="relative">
@ -59,8 +59,8 @@ export const OptionsButton = ({
// setIsOpen(true) // setIsOpen(true)
// }} // }}
> >
<div className="bg-accent rounded-full cursor-pointer text-white w-6 h-6 flex justify-center items-center"> <div className="bg-accent rounded-full cursor-pointer text-white size-6 flex justify-center items-center">
<i className="i-mingcute-settings-6-line text-xl inline-block w-5 h-5" /> <i className="i-mingcute-settings-6-line text-xl inline-block size-5" />
</div> </div>
</Popover.Button> </Popover.Button>
<Transition <Transition
@ -86,11 +86,11 @@ export const OptionsButton = ({
return ( return (
<> <>
<Menu.Button> <Menu.Button>
<div className="bg-accent rounded-full cursor-pointer text-white w-6 h-6 flex items-center justify-center"> <div className="bg-accent rounded-full cursor-pointer text-white size-6 flex items-center justify-center">
{open ? ( {open ? (
<i className="i-mingcute-send-fill text-2xl inline-block w-5 h-5" /> <i className="i-mingcute-send-fill text-2xl inline-block size-5" />
) : ( ) : (
<i className="i-mingcute-send-line text-2xl inline-block w-5 h-5" /> <i className="i-mingcute-send-line text-2xl inline-block size-5" />
)} )}
</div> </div>
</Menu.Button> </Menu.Button>

View File

@ -323,188 +323,185 @@ export const PagesManager = ({ type }: { type: NoteType }) => {
<EmptyState resource={type} /> <EmptyState resource={type} />
)} )}
{pages.data?.pages.map( {pages.data?.pages.map((page) =>
(page) => page.list?.map((page) => {
page.list?.map((page) => { currentLength++
currentLength++ const isPortfolio =
const isPortfolio = page.metadata?.content?.tags?.[0] === "portfolio"
page.metadata?.content?.tags?.[0] === "portfolio" const externalLink = page.metadata?.content?.external_urls?.[0]
const externalLink = page.metadata?.content?.external_urls?.[0] const platform = Object.values(PlatformsSyncMap).find(
const platform = Object.values(PlatformsSyncMap).find( (p) =>
(p) => p.portfolioDomain &&
p.portfolioDomain && externalLink?.startsWith(p.portfolioDomain),
externalLink?.startsWith(p.portfolioDomain), )
)
return ( return (
<Link <Link
key={page.transactionHash || page.draftKey} key={page.transactionHash || page.draftKey}
href={getPageEditLink(page)} href={getPageEditLink(page)}
className="group relative hover:bg-zinc-100 rounded-lg py-4 px-3 transition-colors -mx-3 flex max-sm:flex-col gap-4" className="group relative hover:bg-zinc-100 rounded-lg py-4 px-3 transition-colors -mx-3 flex max-sm:flex-col gap-4"
> >
<PostCover <PostCover
uniqueKey={`${page.characterId}-${page.noteId}`} uniqueKey={`${page.characterId}-${page.noteId}`}
images={page.metadata?.content.images} images={page.metadata?.content.images}
title={page.metadata?.content?.title} title={page.metadata?.content?.title}
className="rounded-lg sm:w-48" className="rounded-lg sm:w-48"
/> />
<div className="min-w-0 flex-1 flex flex-col justify-between"> <div className="min-w-0 flex-1 flex flex-col justify-between">
<div className="xlog-post-title font-bold text-base text-zinc-700"> <div className="xlog-post-title font-bold text-base text-zinc-700">
<span>{page.metadata?.content?.title}</span> <span>{page.metadata?.content?.title}</span>
</div>
<div
className="xlog-post-excerpt text-zinc-500 line-clamp-1 text-sm"
style={{
wordBreak: "break-word",
}}
>
{page.metadata?.content?.summary}
{page.metadata?.content?.summary && "..."}
</div>
<div className="xlog-post-meta text-zinc-400 flex items-center text-[13px] h-[26px] truncate">
{isPortfolio ? (
<>
<Tooltip
label={`${platform?.name || platform}`}
className="text-sm"
>
<span className="inline-flex items-center space-x-[6px]">
{platform?.icon && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={platform?.icon}
alt={platform?.name}
width={16}
height={16}
/>
)}
<span>{t("Portfolio")}</span>
</span>
</Tooltip>
</>
) : (
<>
{!!page.metadata?.content?.tags?.[1] && (
<span className="xlog-post-tags border transition-colors text-zinc-500 inline-flex items-center bg-zinc-100 rounded-full px-2 py-[1.5px] truncate text-xs sm:text-[13px] mr-2">
<i className="i-mingcute-tag-line mr-[2px]" />
{page.metadata?.content?.tags?.[1]}
</span>
)}
<span className="xlog-post-word-count sm:inline-flex items-center hidden mr-2">
<i className="i-mingcute-time-line mr-[2px]" />
<span
style={{
wordSpacing: "-.2ch",
}}
>
{page.metadata?.content?.readingTime} {t("min")}
</span>
</span>
{!!page.stat?.viewDetailCount && (
<span className="xlog-post-views inline-flex items-center">
<i className="i-mingcute-eye-line mr-[2px]" />
<span>{page.stat?.viewDetailCount}</span>
</span>
)}
</>
)}
</div>
<div className="text-zinc-400 text-sm">
<span className="capitalize">
{t(getPageVisibility(page))}
</span>
<span className="mx-2">·</span>
<span>
{getPageVisibility(page) === PageVisibilityEnum.Draft
? date.formatDate(page.updatedAt)
: date.formatDate(
page.metadata?.content?.date_published || "",
)}
</span>
{pinnedPage.noteId === page.noteId && (
<>
<span className="mx-2">·</span>
<span>
<i className="i-mingcute-pin-2-fill translate-y-[18%]" />{" "}
{t("Pinned")}
</span>
</>
)}
</div>
</div> </div>
<div className="shrink-0 flex gap-2 sm:self-center sm:ml-auto"> <div
<button className="xlog-post-excerpt text-zinc-500 line-clamp-1 text-sm"
className={cn( style={{
`text-gray-400 relative z-10 w-8 h-8 rounded inline-flex group-hover:visible justify-center items-center`, wordBreak: "break-word",
}}
>
{page.metadata?.content?.summary}
{page.metadata?.content?.summary && "..."}
</div>
<div className="xlog-post-meta text-zinc-400 flex items-center text-[13px] h-[26px] truncate">
{isPortfolio ? (
<>
<Tooltip
label={`${platform?.name || platform}`}
className="text-sm"
>
<span className="inline-flex items-center space-x-[6px]">
{platform?.icon && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={platform?.icon}
alt={platform?.name}
width={16}
height={16}
/>
)}
<span>{t("Portfolio")}</span>
</span>
</Tooltip>
</>
) : (
<>
{!!page.metadata?.content?.tags?.[1] && (
<span className="xlog-post-tags border transition-colors text-zinc-500 inline-flex items-center bg-zinc-100 rounded-full px-2 py-[1.5px] truncate text-xs sm:text-[13px] mr-2">
<i className="i-mingcute-tag-line mr-[2px]" />
{page.metadata?.content?.tags?.[1]}
</span>
)}
<span className="xlog-post-word-count sm:inline-flex items-center hidden mr-2">
<i className="i-mingcute-time-line mr-[2px]" />
<span
style={{
wordSpacing: "-.2ch",
}}
>
{page.metadata?.content?.readingTime} {t("min")}
</span>
</span>
{!!page.stat?.viewDetailCount && (
<span className="xlog-post-views inline-flex items-center">
<i className="i-mingcute-eye-line mr-[2px]" />
<span>{page.stat?.viewDetailCount}</span>
</span>
)}
</>
)}
</div>
<div className="text-zinc-400 text-sm">
<span className="capitalize">
{t(getPageVisibility(page))}
</span>
<span className="mx-2">·</span>
<span>
{getPageVisibility(page) === PageVisibilityEnum.Draft
? date.formatDate(page.updatedAt)
: date.formatDate(
page.metadata?.content?.date_published || "",
)}
</span>
{pinnedPage.noteId === page.noteId && (
<>
<span className="mx-2">·</span>
<span>
<i className="i-mingcute-pin-2-fill translate-y-[18%]" />{" "}
{t("Pinned")}
</span>
</>
)}
</div>
</div>
<div className="shrink-0 flex gap-2 sm:self-center sm:ml-auto">
<button
className={cn(
`text-gray-400 relative z-10 size-8 rounded inline-flex group-hover:visible justify-center items-center`,
batchSelected.includes(page.noteId || page.draftKey || 0)
? "bg-gray-200"
: `hover:bg-gray-200`,
)}
onClick={(e) => {
e.preventDefault()
// Toggle selection
if (
batchSelected.includes( batchSelected.includes(
page.noteId || page.draftKey || 0, page.noteId || page.draftKey || 0,
) )
? "bg-gray-200" ) {
: `hover:bg-gray-200`, // Deselect
)} setBatchSelected(
onClick={(e) => { batchSelected.filter(
e.preventDefault() (pageId) =>
// Toggle selection pageId !== page.noteId || page.draftKey || 0,
if ( ),
batchSelected.includes( )
page.noteId || page.draftKey || 0, } else {
) // Do select
) { setBatchSelected([
// Deselect ...batchSelected,
setBatchSelected( page.noteId || page.draftKey || 0,
batchSelected.filter( ])
(pageId) => }
pageId !== page.noteId || page.draftKey || 0, }}
), >
) <i
} else { className={`${
// Do select batchSelected.includes(
setBatchSelected([ page.noteId || page.draftKey || 0,
...batchSelected, )
page.noteId || page.draftKey || 0, ? "i-mingcute-check-fill"
]) : "i-mingcute-add-line"
} } text-lg`}
}} />
> </button>
<i <Menu>
className={`${ {({ open, close }) => (
batchSelected.includes( <>
page.noteId || page.draftKey || 0, <Menu.Button as={Fragment}>
) <button
? "i-mingcute-check-fill" className={cn(
: "i-mingcute-add-line" `text-gray-400 relative z-10 size-8 rounded inline-flex group-hover:visible justify-center items-center`,
} text-lg`} open ? `bg-gray-200` : `hover:bg-gray-200`,
/> )}
</button> onClick={(e) => {
<Menu> e.stopPropagation()
{({ open, close }) => ( }}
<> >
<Menu.Button as={Fragment}> <i className="i-mingcute-more-1-line text-2xl" />
<button </button>
className={cn( </Menu.Button>
`text-gray-400 relative z-10 w-8 h-8 rounded inline-flex group-hover:visible justify-center items-center`,
open ? `bg-gray-200` : `hover:bg-gray-200`,
)}
onClick={(e) => {
e.stopPropagation()
}}
>
<i className="i-mingcute-more-1-line text-2xl" />
</button>
</Menu.Button>
<PagesManagerMenu <PagesManagerMenu
type={type} type={type}
page={page} page={page}
onClick={close} onClick={close}
/> />
</> </>
)} )}
</Menu> </Menu>
</div> </div>
</Link> </Link>
) )
}), }),
)} )}
{pages.hasNextPage && ( {pages.hasNextPage && (

View File

@ -42,11 +42,10 @@ export const PagesManagerBatchSelectActionTab = ({
onClick: () => { onClick: () => {
// Get all page IDs // Get all page IDs
const allIDs: (string | number)[] = [] const allIDs: (string | number)[] = []
pages?.pages.map( pages?.pages.map((page) =>
(page) => page.list?.map((page) => {
page.list?.map((page) => { allIDs.push(getPageId(page))
allIDs.push(getPageId(page)) }),
}),
) )
setBatchSelected(allIDs) setBatchSelected(allIDs)
}, },
@ -65,13 +64,12 @@ export const PagesManagerBatchSelectActionTab = ({
// Find all selected // Find all selected
const selectedPages: ExpandedNote[] = [] const selectedPages: ExpandedNote[] = []
pages?.pages.map( pages?.pages.map((page) =>
(page) => page.list?.map((page) => {
page.list?.map((page) => { if (batchSelected.includes(getPageId(page))) {
if (batchSelected.includes(getPageId(page))) { selectedPages.push(page)
selectedPages.push(page) }
} }),
}),
) )
// Convert all // TODO: use multicall to optimize // Convert all // TODO: use multicall to optimize
@ -132,13 +130,12 @@ export const PagesManagerBatchSelectActionTab = ({
// Find all selected // Find all selected
const selectedPages: ExpandedNote[] = [] const selectedPages: ExpandedNote[] = []
pages?.pages.map( pages?.pages.map((page) =>
(page) => page.list?.map((page) => {
page.list?.map((page) => { if (batchSelected.includes(page.noteId || page.draftKey || 0)) {
if (batchSelected.includes(page.noteId || page.draftKey || 0)) { selectedPages.push(page)
selectedPages.push(page) }
} }),
}),
) )
// Delete all // TODO: use multicall to optimize // Delete all // TODO: use multicall to optimize

View File

@ -194,7 +194,7 @@ export const HomeFeed = ({ type }: { type?: FeedType }) => {
<span <span
className={`${ className={`${
aiFiltering ? "translate-x-6" : "translate-x-1" aiFiltering ? "translate-x-6" : "translate-x-1"
} inline-block h-4 w-4 rounded-full bg-white transition`} } inline-block size-4 rounded-full bg-white transition`}
/> />
</Switch> </Switch>
</div> </div>

View File

@ -66,7 +66,7 @@ export async function HomeSidebar({ hideSearch }: { hideSearch?: boolean }) {
className="inline-flex align-middle w-full" className="inline-flex align-middle w-full"
> >
<CharacterFloatCard siteId={site.handle}> <CharacterFloatCard siteId={site.handle}>
<span className="w-10 h-10 inline-block"> <span className="size-10 inline-block">
<Avatar <Avatar
cid={site?.characterId} cid={site?.characterId}
images={site?.metadata?.content?.avatars || []} images={site?.metadata?.content?.avatars || []}

View File

@ -18,7 +18,7 @@ export function Integration() {
const integrations = [ const integrations = [
{ {
name: "RSS", name: "RSS",
icon: <RssIcon className="w-full h-full text-orange-500" />, icon: <RssIcon className="size-full text-orange-500" />,
url: url:
getSiteLink({ getSiteLink({
subdomain: "xlog", subdomain: "xlog",
@ -34,42 +34,42 @@ export function Integration() {
}, },
{ {
name: "xChar", name: "xChar",
icon: <XCharLogo className="w-full h-full" />, icon: <XCharLogo className="size-full" />,
url: "https://xchar.app/", url: "https://xchar.app/",
}, },
{ {
name: "xFeed", name: "xFeed",
icon: <XFeedLogo className="w-full h-full" />, icon: <XFeedLogo className="size-full" />,
url: "https://xfeed.app/", url: "https://xfeed.app/",
}, },
{ {
name: "xSync", name: "xSync",
icon: <XSyncLogo className="w-full h-full" />, icon: <XSyncLogo className="size-full" />,
url: "https://xsync.app/", url: "https://xsync.app/",
}, },
{ {
name: "xShop", name: "xShop",
icon: <XShopLogo className="w-full h-full" />, icon: <XShopLogo className="size-full" />,
text: "Coming soon", text: "Coming soon",
}, },
{ {
name: "Crossbell Scan", name: "Crossbell Scan",
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />, icon: <CrossbellChainLogo className="size-full text-[#E7B75B]" />,
url: "https://scan.crossbell.io/", url: "https://scan.crossbell.io/",
}, },
{ {
name: "Crossbell Faucet", name: "Crossbell Faucet",
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />, icon: <CrossbellChainLogo className="size-full text-[#E7B75B]" />,
url: "https://faucet.crossbell.io/", url: "https://faucet.crossbell.io/",
}, },
{ {
name: "Crossbell Export", name: "Crossbell Export",
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />, icon: <CrossbellChainLogo className="size-full text-[#E7B75B]" />,
url: "https://export.crossbell.io/", url: "https://export.crossbell.io/",
}, },
{ {
name: "Crossbell SDK", name: "Crossbell SDK",
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />, icon: <CrossbellChainLogo className="size-full text-[#E7B75B]" />,
url: "https://crossbell-box.github.io/crossbell.js/", url: "https://crossbell-box.github.io/crossbell.js/",
}, },
{ {
@ -116,7 +116,7 @@ export function Integration() {
href={item.url} href={item.url}
className="w-full flex items-center flex-col justify-center" className="w-full flex items-center flex-col justify-center"
> >
<div className="w-12 h-12 rounded-md overflow-hidden"> <div className="size-12 rounded-md overflow-hidden">
{item.icon} {item.icon}
</div> </div>
<div className="font-medium sm:text-lg mt-2 text-center"> <div className="font-medium sm:text-lg mt-2 text-center">
@ -125,8 +125,8 @@ export function Integration() {
</UniLink> </UniLink>
) : ( ) : (
<Tooltip label={item.text!}> <Tooltip label={item.text!}>
<div className="w-full h-full flex items-center flex-col justify-center"> <div className="size-full flex items-center flex-col justify-center">
<div className="w-12 h-12 rounded-md overflow-hidden"> <div className="size-12 rounded-md overflow-hidden">
{item.icon} {item.icon}
</div> </div>
<div className="font-medium sm:text-lg mt-2 text-center"> <div className="font-medium sm:text-lg mt-2 text-center">

View File

@ -46,11 +46,11 @@ export default function PostCover({
forceToAxis: true, forceToAxis: true,
}} }}
modules={[Navigation, Scrollbar, Mousewheel]} modules={[Navigation, Scrollbar, Mousewheel]}
className="w-full h-full" className="size-full"
> >
{images?.map((image) => ( {images?.map((image) => (
<SwiperSlide key={image}> <SwiperSlide key={image}>
<div className="text-[0px] w-full h-full"> <div className="text-[0px] size-full">
<Image <Image
className={cn( className={cn(
"object-cover w-full sm:group-hover:scale-105 sm:transition-transform sm:duration-400 sm:ease-in-out bg-white", "object-cover w-full sm:group-hover:scale-105 sm:transition-transform sm:duration-400 sm:ease-in-out bg-white",
@ -88,7 +88,7 @@ export default function PostCover({
></Image> ></Image>
) : ( ) : (
<div className="xlog-post-cover rounded-t-2xl overflow-hidden flex items-center relative w-full aspect-video border-b"> <div className="xlog-post-cover rounded-t-2xl overflow-hidden flex items-center relative w-full aspect-video border-b">
<div className="sm:group-hover:scale-105 sm:transition-transform sm:duration-400 sm:ease-in-out p-3 w-full h-full text-center flex items-center justify-center"> <div className="sm:group-hover:scale-105 sm:transition-transform sm:duration-400 sm:ease-in-out p-3 size-full text-center flex items-center justify-center">
<div className="text-zinc-600 text-xl font-extrabold"> <div className="text-zinc-600 text-xl font-extrabold">
{title} {title}
</div> </div>

View File

@ -37,7 +37,7 @@ export default async function PromotionLinks({
placement="bottom" placement="bottom"
> >
<UniLink href="https://oia.xlog.app"> <UniLink href="https://oia.xlog.app">
<DevicePhoneMobileIcon className="w-[23px] h-[23px]" /> <DevicePhoneMobileIcon className="size-[23px]" />
</UniLink> </UniLink>
</Tooltip> </Tooltip>
{GITHUB_LINK && ( {GITHUB_LINK && (

View File

@ -54,7 +54,7 @@ export function ShowCase() {
className="inline-flex align-middle w-full" className="inline-flex align-middle w-full"
> >
<CharacterFloatCard siteId={site.handle}> <CharacterFloatCard siteId={site.handle}>
<span className="w-14 h-14 inline-block"> <span className="size-14 inline-block">
<Image <Image
className="rounded-full" className="rounded-full"
src={site.metadata?.content?.avatars?.[0] || DEFAULT_AVATAR} src={site.metadata?.content?.avatars?.[0] || DEFAULT_AVATAR}

View File

@ -37,7 +37,7 @@ export default function ConnectedAccounts({
key={match[2] + match[1]} key={match[2] + match[1]}
platform={match[2]} platform={match[2]}
username={match[1]} username={match[1]}
className="w-4 h-4 sm:w-5 sm:h-5" className="size-4 sm:size-5"
></Platform> ></Platform>
) )
} }

View File

@ -68,10 +68,10 @@ export default function ShortList({
}` }`
: "") + `/${post.metadata?.content?.slug}` : "") + `/${post.metadata?.content?.slug}`
} }
className="inline-block w-full h-full rounded-2xl overflow-hidden" className="inline-block size-full rounded-2xl overflow-hidden"
> >
<Image <Image
className="object-cover w-full h-full sm:hover:scale-105 sm:transition-transform sm:duration-400 sm:ease-in-out" className="object-cover size-full sm:hover:scale-105 sm:transition-transform sm:duration-400 sm:ease-in-out"
alt="cover" alt="cover"
src={post.metadata?.content.images?.[0] || ""} src={post.metadata?.content.images?.[0] || ""}
width={170} width={170}
@ -87,7 +87,7 @@ export default function ShortList({
> >
<Link <Link
href="/shorts" href="/shorts"
className="bg-white rounded-full z-[1] w-8 h-8 flex items-center justify-center" className="bg-white rounded-full z-[1] size-8 flex items-center justify-center"
> >
<i className="i-mingcute-right-fill" /> <i className="i-mingcute-right-fill" />
</Link> </Link>

View File

@ -48,18 +48,18 @@ export const SiteHeaderMenu = ({
const moreMenuItems = [ const moreMenuItems = [
{ {
text: "View on xChar", text: "View on xChar",
icon: <XCharLogo className="w-full h-full" />, icon: <XCharLogo className="size-full" />,
url: `${CSB_XCHAR}/${handle}`, url: `${CSB_XCHAR}/${handle}`,
}, },
{ {
text: "View on xFeed", text: "View on xFeed",
icon: <XFeedLogo className="w-full h-full" />, icon: <XFeedLogo className="size-full" />,
url: `${CSB_XFEED}/u/${handle}`, url: `${CSB_XFEED}/u/${handle}`,
}, },
{ {
text: "View on Hoot It", text: "View on Hoot It",
icon: ( icon: (
<div className="w-full h-full"> <div className="size-full">
<Image <Image
alt="Hoot It" alt="Hoot It"
src="/assets/hoot.svg" src="/assets/hoot.svg"
@ -73,13 +73,13 @@ export const SiteHeaderMenu = ({
}, },
{ {
text: "View on Crossbell Scan", text: "View on Crossbell Scan",
icon: <BlockchainIcon className="fill-[#c09526] w-full h-full" />, icon: <BlockchainIcon className="fill-[#c09526] size-full" />,
url: `${CSB_SCAN}/address/${owner}`, url: `${CSB_SCAN}/address/${owner}`,
}, },
{ {
text: "Subscribe to JSON Feed", text: "Subscribe to JSON Feed",
icon: ( icon: (
<div className="w-full h-full"> <div className="size-full">
<Image <Image
alt="JSON Feed" alt="JSON Feed"
src="/assets/json-feed.png" src="/assets/json-feed.png"
@ -93,7 +93,7 @@ export const SiteHeaderMenu = ({
}, },
{ {
text: "Subscribe to RSS", text: "Subscribe to RSS",
icon: <RssIcon className="w-full h-full text-[#ee832f]" />, icon: <RssIcon className="size-full text-[#ee832f]" />,
url: "/feed", url: "/feed",
out: true, out: true,
}, },
@ -155,7 +155,7 @@ export const SiteHeaderMenu = ({
: item.onClick?.() : item.onClick?.()
} }
> >
<span className="fill-gray-500 flex w-6 h-6 text-2xl"> <span className="fill-gray-500 flex size-6 text-2xl">
{item.icon} {item.icon}
</span> </span>
</Button> </Button>

View File

@ -40,7 +40,7 @@ export const AvatarStack: FC<AvatarStackProps> = (props) => {
})} })}
{count > showCount && ( {count > showCount && (
<li className="inline-block"> <li className="inline-block">
<div className="relative align-middle border-2 border-white w-10 h-10 rounded-full inline-flex bg-gray-100 items-center justify-center text-gray-400 font-medium"> <div className="relative align-middle border-2 border-white size-10 rounded-full inline-flex bg-gray-100 items-center justify-center text-gray-400 font-medium">
+{count - showCount} +{count - showCount}
</div> </div>
</li> </li>

View File

@ -92,7 +92,7 @@ export const FABBase = (
return ( return (
<button <button
className={cn( className={cn(
"bg-white inline-flex w-8 h-8 mt-2 items-center justify-center rounded-md border border-accent text-accent opacity-50 hover:opacity-100 focus:opacity-100 focus:outline-none transition-all duration-300", "bg-white inline-flex size-8 mt-2 items-center justify-center rounded-md border border-accent text-accent opacity-50 hover:opacity-100 focus:opacity-100 focus:outline-none transition-all duration-300",
(!show || appearTransition) && "translate-x-[60px] xlog-fab-inactive", (!show || appearTransition) && "translate-x-[60px] xlog-fab-inactive",
!mounted && "hidden", !mounted && "hidden",
className, className,

View File

@ -121,7 +121,7 @@ export const Image = ({
/> />
) : ( ) : (
<span <span
className="inline-flex justify-center w-full h-full overflow-hidden" className="inline-flex justify-center size-full overflow-hidden"
style={ style={
autoSize autoSize
? { ? {
@ -131,7 +131,7 @@ export const Image = ({
} }
> >
<span <span
className="inline-flex justify-center relative w-full h-full" className="inline-flex justify-center relative size-full"
style={autoSize ? { paddingTop } : {}} style={autoSize ? { paddingTop } : {}}
> >
<NextImage <NextImage

View File

@ -165,7 +165,7 @@ export const ImageUploader = forwardRef(function ImageUploader(
(typeof inputProps.value === "object" && (typeof inputProps.value === "object" &&
inputProps.value.mime_type?.split("/")[0] === "video" ? ( inputProps.value.mime_type?.split("/")[0] === "video" ? (
<video <video
className="max-w-screen-md mx-auto object-cover h-full w-full" className="max-w-screen-md mx-auto object-cover size-full"
src={toGateway(imageUrl)} src={toGateway(imageUrl)}
autoPlay autoPlay
muted muted
@ -180,7 +180,7 @@ export const ImageUploader = forwardRef(function ImageUploader(
/> />
))} ))}
{!imageUrl && ( {!imageUrl && (
<div className="w-full h-full flex justify-center items-center text-zinc-500 text-center bg-white"> <div className="size-full flex justify-center items-center text-zinc-500 text-center bg-white">
{t("Click to select files")} {t("Click to select files")}
</div> </div>
)} )}
@ -193,7 +193,7 @@ export const ImageUploader = forwardRef(function ImageUploader(
{imageUrl && hasClose && ( {imageUrl && hasClose && (
<div <div
onClick={clear} onClick={clear}
className="w-8 h-8 absolute top-4 right-4 shadow bg-white rounded-full cursor-pointer" className="size-8 absolute top-4 right-4 shadow bg-white rounded-full cursor-pointer"
> >
<XMarkIcon /> <XMarkIcon />
</div> </div>

View File

@ -102,7 +102,7 @@ export const Input = forwardRef(function Input<
/> />
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2"> <Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon <ChevronUpDownIcon
className="h-5 w-5 text-gray-400" className="size-5 text-gray-400"
aria-hidden="true" aria-hidden="true"
/> />
</Combobox.Button> </Combobox.Button>
@ -146,7 +146,7 @@ export const Input = forwardRef(function Input<
}`} }`}
> >
<CheckIcon <CheckIcon
className="h-5 w-5" className="size-5"
aria-hidden="true" aria-hidden="true"
/> />
</span> </span>

View File

@ -22,7 +22,7 @@ export const Switch = ({ label, checked, setChecked }: SwitchProps) => (
<span <span
className={`${ className={`${
checked ? "translate-x-6" : "translate-x-1" checked ? "translate-x-6" : "translate-x-1"
} inline-block h-4 w-4 rounded-full bg-white transition`} } inline-block size-4 rounded-full bg-white transition`}
/> />
</button> </button>
)} )}

View File

@ -75,7 +75,7 @@ export function TagInput({
key={tag} key={tag}
> >
<span>{tag}</span> <span>{tag}</span>
<XMarkIcon className="h-5 w-5" onClick={() => onDel(tag)} /> <XMarkIcon className="size-5" onClick={() => onDel(tag)} />
</div> </div>
))} ))}
</div> </div>
@ -89,7 +89,7 @@ export function TagInput({
/> />
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2"> <Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon <ChevronUpDownIcon
className="h-5 w-5 text-black" className="size-5 text-black"
aria-hidden="true" aria-hidden="true"
/> />
</Combobox.Button> </Combobox.Button>

View File

@ -67,7 +67,7 @@ const XLogPost: FC<Props> = ({ slug, handle, url }) => {
<div <div
onClick={preventNavigate} onClick={preventNavigate}
className={cn( className={cn(
"w-full h-full mb-2 sm:mb-0 align-middle flex items-center justify-center transition-all duration-500 ease-in-out", "size-full mb-2 sm:mb-0 align-middle flex items-center justify-center transition-all duration-500 ease-in-out",
isExpanded ? "sm:w-[350px]" : "sm:w-[150px]", isExpanded ? "sm:w-[350px]" : "sm:w-[150px]",
)} )}
> >
@ -75,7 +75,7 @@ const XLogPost: FC<Props> = ({ slug, handle, url }) => {
uniqueKey={`short-${page.data?.characterId}-${page.data?.noteId}`} uniqueKey={`short-${page.data?.characterId}-${page.data?.noteId}`}
images={images} images={images}
title={page.data?.metadata?.content?.title} title={page.data?.metadata?.content?.title}
className="rounded-lg w-full aspect-auto border-b-0 h-full" className="rounded-lg size-full aspect-auto border-b-0"
imgClassName="rounded-lg object-contain" imgClassName="rounded-lg object-contain"
/> />
</div> </div>

View File

@ -1 +0,0 @@
export const promiseAll = (obj: Record<string, Promise<any>>) => {}

View File

@ -1,3 +0,0 @@
import { validate } from "uuid"
export const isUUID = (id: string) => validate(id)

View File

@ -411,4 +411,4 @@
"Publishing": "你發佈的內容", "Publishing": "你發佈的內容",
"Support Markdown": "支援 Markdown 語法", "Support Markdown": "支援 Markdown 語法",
"on-chain posting on": "條鏈上發布於" "on-chain posting on": "條鏈上發布於"
} }

View File

@ -338,7 +338,7 @@
"Home": "主页", "Home": "主页",
"Archives": "归档", "Archives": "归档",
"About": "关于", "About": "关于",
"Please upload at least one image" : "请至少上传一张图片。", "Please upload at least one image": "请至少上传一张图片。",
"Write": "写作", "Write": "写作",
"Own": "拥有", "Own": "拥有",
"Earn": "赚取", "Earn": "赚取",

View File

@ -106,8 +106,8 @@ export async function getCommentsBySite(input: {
nestedNotesLimit: 20, nestedNotesLimit: 20,
}) })
notes.list = notes.list.filter( notes.list = notes.list.filter((item) =>
(item) => item.toNote?.metadata?.content?.sources?.includes("xlog"), item.toNote?.metadata?.content?.sources?.includes("xlog"),
) )
return notes return notes

View File

@ -1,18 +0,0 @@
import { NextPage } from "next"
import { AppProps } from "next/app"
import { ReactElement, ReactNode } from "react"
import { DehydratedState } from "@tanstack/react-query"
export type AppPropsWithLayout = AppProps<{
dehydratedState: DehydratedState
}> & {
Component: NextPageWithLayout
}
export type NextPageWithLayout<
P = { dehydratedState: DehydratedState },
IP = P,
> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}