button style improvements, homepage and navbar refactors
This commit is contained in:
parent
a64cc78eed
commit
f3d588c0eb
19 changed files with 214 additions and 201 deletions
|
@ -132,8 +132,9 @@ function Auth({
|
||||||
<Button width={"100%"} type="submit" loading={submitting}>
|
<Button width={"100%"} type="submit" loading={submitting}>
|
||||||
Sign {signText}
|
Sign {signText}
|
||||||
</Button>
|
</Button>
|
||||||
{isGithubEnabled ? <hr style={{ width: "100%" }} /> : null}
|
|
||||||
{isGithubEnabled ? (
|
{isGithubEnabled ? (
|
||||||
|
<>
|
||||||
|
<hr style={{ width: "100%" }} />
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
width="100%"
|
width="100%"
|
||||||
|
@ -151,6 +152,7 @@ function Auth({
|
||||||
>
|
>
|
||||||
Sign {signText.toLowerCase()} with GitHub
|
Sign {signText.toLowerCase()} with GitHub
|
||||||
</Button>
|
</Button>
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.formContentSpace}>
|
<div className={styles.formContentSpace}>
|
||||||
|
|
|
@ -8,12 +8,12 @@ import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
||||||
type Props = {
|
type Props = {
|
||||||
height?: number | string
|
height?: number | string
|
||||||
fileId?: string
|
fileId?: string
|
||||||
content?: string
|
|
||||||
title?: string
|
title?: string
|
||||||
|
children?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function MarkdownPreview({ height = 500, fileId, content = "", title }: Props) {
|
function MarkdownPreview({ height = 500, fileId, title, children }: Props) {
|
||||||
const [preview, setPreview] = useState<string>(content)
|
const [preview, setPreview] = useState<string>(children || "")
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchPost() {
|
async function fetchPost() {
|
||||||
|
@ -24,7 +24,7 @@ function MarkdownPreview({ height = 500, fileId, content = "", title }: Props) {
|
||||||
? undefined
|
? undefined
|
||||||
: JSON.stringify({
|
: JSON.stringify({
|
||||||
title: title || "",
|
title: title || "",
|
||||||
content: content
|
content: children
|
||||||
})
|
})
|
||||||
|
|
||||||
const resp = await fetchWithUser(path, {
|
const resp = await fetchWithUser(path, {
|
||||||
|
@ -43,14 +43,14 @@ function MarkdownPreview({ height = 500, fileId, content = "", title }: Props) {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
fetchPost()
|
fetchPost()
|
||||||
}, [content, fileId, title])
|
}, [children, fileId, title])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
<StaticPreview preview={preview} height={height} />
|
<StaticPreview height={height}>{preview}</StaticPreview>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -59,16 +59,16 @@ function MarkdownPreview({ height = 500, fileId, content = "", title }: Props) {
|
||||||
export default memo(MarkdownPreview)
|
export default memo(MarkdownPreview)
|
||||||
|
|
||||||
export function StaticPreview({
|
export function StaticPreview({
|
||||||
preview,
|
children,
|
||||||
height = 500
|
height = 500
|
||||||
}: {
|
}: {
|
||||||
preview: string
|
children: string
|
||||||
height: string | number
|
height: string | number
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
className={styles.markdownPreview}
|
className={styles.markdownPreview}
|
||||||
dangerouslySetInnerHTML={{ __html: preview }}
|
dangerouslySetInnerHTML={{ __html: children }}
|
||||||
style={{
|
style={{
|
||||||
height
|
height
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -13,8 +13,8 @@ type Props = RadixTabs.TabsProps & {
|
||||||
handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
|
handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
onPaste?: (e: ClipboardEvent<HTMLTextAreaElement>) => void
|
onPaste?: (e: ClipboardEvent<HTMLTextAreaElement>) => void
|
||||||
title?: string
|
title?: string
|
||||||
content?: string
|
staticPreview?: string
|
||||||
preview?: string
|
children: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DocumentTabs({
|
export default function DocumentTabs({
|
||||||
|
@ -23,8 +23,8 @@ export default function DocumentTabs({
|
||||||
handleOnContentChange,
|
handleOnContentChange,
|
||||||
onPaste,
|
onPaste,
|
||||||
title,
|
title,
|
||||||
content,
|
staticPreview: preview,
|
||||||
preview,
|
children,
|
||||||
...props
|
...props
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const codeEditorRef = useRef<TextareaMarkdownRef>(null)
|
const codeEditorRef = useRef<TextareaMarkdownRef>(null)
|
||||||
|
@ -72,7 +72,7 @@ export default function DocumentTabs({
|
||||||
onPaste={onPaste ? onPaste : undefined}
|
onPaste={onPaste ? onPaste : undefined}
|
||||||
ref={codeEditorRef}
|
ref={codeEditorRef}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
value={content}
|
value={children}
|
||||||
onChange={handleOnContentChange}
|
onChange={handleOnContentChange}
|
||||||
// TODO: Textarea should grow to fill parent if height == 100%
|
// TODO: Textarea should grow to fill parent if height == 100%
|
||||||
style={{ flex: 1, minHeight: 350 }}
|
style={{ flex: 1, minHeight: 350 }}
|
||||||
|
@ -83,9 +83,9 @@ export default function DocumentTabs({
|
||||||
</RadixTabs.Content>
|
</RadixTabs.Content>
|
||||||
<RadixTabs.Content value="preview">
|
<RadixTabs.Content value="preview">
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<Preview height={"100%"} title={title} content={content} />
|
<Preview height={"100%"} title={title}>{children}</Preview>
|
||||||
) : (
|
) : (
|
||||||
<StaticPreview height={"100%"} preview={preview || ""} />
|
<StaticPreview height={"100%"}>{preview}</StaticPreview>
|
||||||
)}
|
)}
|
||||||
</RadixTabs.Content>
|
</RadixTabs.Content>
|
||||||
</RadixTabs.Root>
|
</RadixTabs.Root>
|
||||||
|
|
|
@ -45,7 +45,6 @@ const PostFiles = ({ post: _initialPost }: Props) => {
|
||||||
|
|
||||||
const isProtected = post?.visibility === "protected"
|
const isProtected = post?.visibility === "protected"
|
||||||
const hasFetched = post?.files !== undefined
|
const hasFetched = post?.files !== undefined
|
||||||
console.log({ isProtected, hasFetched })
|
|
||||||
if (isProtected && !hasFetched) {
|
if (isProtected && !hasFetched) {
|
||||||
return (
|
return (
|
||||||
<PasswordModalWrapper
|
<PasswordModalWrapper
|
||||||
|
|
|
@ -112,7 +112,7 @@ const Document = ({ skeleton, ...props }: Props) => {
|
||||||
{/* Not /api/ because of rewrites defined in next.config.mjs */}
|
{/* Not /api/ because of rewrites defined in next.config.mjs */}
|
||||||
<DocumentTabs
|
<DocumentTabs
|
||||||
defaultTab={initialTab}
|
defaultTab={initialTab}
|
||||||
preview={preview}
|
staticPreview={preview}
|
||||||
content={content}
|
content={content}
|
||||||
isEditing={false}
|
isEditing={false}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
padding: var(--gap-half) var(--gap);
|
/* padding: var(--gap-half) var(--gap); */
|
||||||
color: var(--darker-gray);
|
color: var(--darker-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,11 +31,12 @@ const Button = forwardRef<HTMLButtonElement, Props>(
|
||||||
disabled = false,
|
disabled = false,
|
||||||
iconRight,
|
iconRight,
|
||||||
iconLeft,
|
iconLeft,
|
||||||
height,
|
height = 40,
|
||||||
width,
|
width,
|
||||||
padding,
|
padding = 10,
|
||||||
margin,
|
margin,
|
||||||
loading,
|
loading,
|
||||||
|
style,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
|
@ -49,7 +50,7 @@ const Button = forwardRef<HTMLButtonElement, Props>(
|
||||||
})}
|
})}
|
||||||
disabled={disabled || loading}
|
disabled={disabled || loading}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{ height, width, margin, padding }}
|
style={{ height, width, margin, padding, ...style }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children && iconLeft && (
|
{children && iconLeft && (
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
border: 1px solid var(--light-gray);
|
border: 1px solid var(--light-gray);
|
||||||
width: auto;
|
width: 100%;
|
||||||
height: auto;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,11 +38,6 @@
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
transition: opacity 0.2s ease-in-out;
|
transition: opacity 0.2s ease-in-out;
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header:not(.loading) {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectContent {
|
.selectContent {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import Button from "@components/button"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
import {
|
import {
|
||||||
GitHub,
|
|
||||||
Home,
|
Home,
|
||||||
Menu,
|
Menu,
|
||||||
Moon,
|
Moon,
|
||||||
|
@ -17,13 +16,13 @@ import {
|
||||||
Settings,
|
Settings,
|
||||||
Sun,
|
Sun,
|
||||||
User,
|
User,
|
||||||
UserPlus,
|
|
||||||
UserX
|
UserX
|
||||||
} from "react-feather"
|
} from "react-feather"
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
|
||||||
import buttonStyles from "@components/button/button.module.css"
|
import buttonStyles from "@components/button/button.module.css"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useSessionSWR } from "@lib/use-session-swr"
|
import { useSessionSWR } from "@lib/use-session-swr"
|
||||||
|
import Skeleton from "@components/skeleton"
|
||||||
|
|
||||||
type Tab = {
|
type Tab = {
|
||||||
name: string
|
name: string
|
||||||
|
@ -34,7 +33,7 @@ type Tab = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { isAuthenticated, isAdmin, isLoading, mutate } = useSessionSWR()
|
const { isAdmin, isAuthenticated, isLoading, mutate } = useSessionSWR()
|
||||||
|
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { setTheme, resolvedTheme } = useTheme()
|
const { setTheme, resolvedTheme } = useTheme()
|
||||||
|
@ -52,6 +51,7 @@ const Header = () => {
|
||||||
aria-label={tab.name}
|
aria-label={tab.name}
|
||||||
aria-current={isActive ? "page" : undefined}
|
aria-current={isActive ? "page" : undefined}
|
||||||
data-tab={tab.value}
|
data-tab={tab.value}
|
||||||
|
width="auto"
|
||||||
>
|
>
|
||||||
{tab.name ? tab.name : undefined}
|
{tab.name ? tab.name : undefined}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -59,7 +59,7 @@ const Header = () => {
|
||||||
} else if (tab.href) {
|
} else if (tab.href) {
|
||||||
return (
|
return (
|
||||||
<Link key={tab.value} href={tab.href} data-tab={tab.value}>
|
<Link key={tab.value} href={tab.href} data-tab={tab.value}>
|
||||||
<Button className={activeStyle} iconLeft={tab.icon}>
|
<Button className={activeStyle} iconLeft={tab.icon} width="auto">
|
||||||
{tab.name ? tab.name : undefined}
|
{tab.name ? tab.name : undefined}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -69,12 +69,12 @@ const Header = () => {
|
||||||
|
|
||||||
const pages = useMemo(() => {
|
const pages = useMemo(() => {
|
||||||
const defaultPages: Tab[] = [
|
const defaultPages: Tab[] = [
|
||||||
{
|
// {
|
||||||
name: "GitHub",
|
// name: "GitHub",
|
||||||
href: "https://github.com/maxleiter/drift",
|
// href: "https://github.com/maxleiter/drift",
|
||||||
icon: <GitHub />,
|
// icon: <GitHub />,
|
||||||
value: "github"
|
// value: "github"
|
||||||
}
|
// }
|
||||||
]
|
]
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
|
@ -95,8 +95,37 @@ const Header = () => {
|
||||||
value: "theme"
|
value: "theme"
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isAuthenticated)
|
// the is loading case is handled in the JSX
|
||||||
|
if (!isLoading) {
|
||||||
|
if (isAuthenticated) {
|
||||||
|
defaultPages.push({
|
||||||
|
name: "Sign Out",
|
||||||
|
icon: <UserX />,
|
||||||
|
value: "signout",
|
||||||
|
onClick: () => {
|
||||||
|
mutate(undefined)
|
||||||
|
signOut({
|
||||||
|
callbackUrl: "/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
defaultPages.push({
|
||||||
|
name: "Sign in",
|
||||||
|
icon: <User />,
|
||||||
|
value: "signin",
|
||||||
|
href: "/signin"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
name: "Home",
|
||||||
|
icon: <Home />,
|
||||||
|
value: "home",
|
||||||
|
href: "/home"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "New",
|
name: "New",
|
||||||
icon: <PlusCircle />,
|
icon: <PlusCircle />,
|
||||||
|
@ -115,56 +144,32 @@ const Header = () => {
|
||||||
value: "settings",
|
value: "settings",
|
||||||
href: "/settings"
|
href: "/settings"
|
||||||
},
|
},
|
||||||
...defaultPages,
|
...defaultPages
|
||||||
{
|
|
||||||
name: "Sign Out",
|
|
||||||
icon: <UserX />,
|
|
||||||
value: "signout",
|
|
||||||
onClick: () => {
|
|
||||||
mutate(undefined)
|
|
||||||
signOut({
|
|
||||||
callbackUrl: "/"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
else
|
}, [isAdmin, resolvedTheme, isLoading, setTheme, isAuthenticated, mutate])
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: "Home",
|
|
||||||
icon: <Home />,
|
|
||||||
value: "home",
|
|
||||||
href: "/"
|
|
||||||
},
|
|
||||||
...defaultPages,
|
|
||||||
{
|
|
||||||
name: "Sign in",
|
|
||||||
icon: <User />,
|
|
||||||
value: "signin",
|
|
||||||
href: "/signin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Sign up",
|
|
||||||
icon: <UserPlus />,
|
|
||||||
value: "signup",
|
|
||||||
href: "/signup"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, [isAdmin, resolvedTheme, isAuthenticated, setTheme, mutate])
|
|
||||||
|
|
||||||
const buttons = pages.map(getButton)
|
const buttons = pages.map(getButton)
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
buttons.push(
|
||||||
|
<Button iconLeft={<User />} key="loading">
|
||||||
|
Sign{" "}
|
||||||
|
<Skeleton
|
||||||
|
width={20}
|
||||||
|
height={15}
|
||||||
|
style={{ display: "inline-block", verticalAlign: "middle" }}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this is a hack to close the radix ui menu when a next link is clicked
|
// TODO: this is a hack to close the radix ui menu when a next link is clicked
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }))
|
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header className={styles.header}>
|
||||||
className={clsx(styles.header, {
|
|
||||||
[styles.loading]: isLoading
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className={styles.tabs}>
|
<div className={styles.tabs}>
|
||||||
<div className={styles.buttons}>{buttons}</div>
|
<div className={styles.buttons}>{buttons}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { ListItemSkeleton } from "./list-item-skeleton"
|
||||||
import Link from "@components/link"
|
import Link from "@components/link"
|
||||||
import debounce from "lodash.debounce"
|
import debounce from "lodash.debounce"
|
||||||
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
||||||
|
import { Stack } from "@components/stack"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialPosts: string | PostWithFiles[]
|
initialPosts: string | PostWithFiles[]
|
||||||
|
@ -63,7 +64,6 @@ const PostList = ({
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
console.log(json)
|
|
||||||
setPosts(json)
|
setPosts(json)
|
||||||
setSearching(false)
|
setSearching(false)
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ const PostList = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<Stack className={styles.container} alignItems="center">
|
||||||
{!hideSearch && (
|
{!hideSearch && (
|
||||||
<div className={styles.searchContainer}>
|
<div className={styles.searchContainer}>
|
||||||
<Input
|
<Input
|
||||||
|
@ -122,7 +122,6 @@ const PostList = ({
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
{!showSkeleton && posts && posts.length > 0 ? (
|
{!showSkeleton && posts && posts.length > 0 ? (
|
||||||
<div>
|
|
||||||
<ul>
|
<ul>
|
||||||
{posts.map((post) => {
|
{posts.map((post) => {
|
||||||
return (
|
return (
|
||||||
|
@ -136,9 +135,8 @@ const PostList = ({
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container > * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.container ul li {
|
.container ul li {
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,18 @@ import styles from "./skeleton.module.css"
|
||||||
export default function Skeleton({
|
export default function Skeleton({
|
||||||
width = 100,
|
width = 100,
|
||||||
height = 24,
|
height = 24,
|
||||||
borderRadius = 4
|
borderRadius = 4,
|
||||||
|
style
|
||||||
}: {
|
}: {
|
||||||
width?: number | string
|
width?: number | string
|
||||||
height?: number | string
|
height?: number | string
|
||||||
borderRadius?: number | string
|
borderRadius?: number | string
|
||||||
|
style?: React.CSSProperties
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.skeleton} style={{ width, height, borderRadius }} />
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width, height, borderRadius, ...style }}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ import Layout from "@components/layout"
|
||||||
import { Toasts } from "@components/toasts"
|
import { Toasts } from "@components/toasts"
|
||||||
import Header from "@components/header"
|
import Header from "@components/header"
|
||||||
import { Inter } from "@next/font/google"
|
import { Inter } from "@next/font/google"
|
||||||
import { PropsWithChildren } from "react"
|
import { PropsWithChildren, Suspense } from "react"
|
||||||
|
import { Spinner } from "@components/spinner"
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
||||||
|
|
||||||
|
@ -19,7 +20,9 @@ export default async function RootLayout({
|
||||||
<Toasts />
|
<Toasts />
|
||||||
<Layout>
|
<Layout>
|
||||||
<Providers>
|
<Providers>
|
||||||
|
<Suspense fallback={<Spinner />}>
|
||||||
<Header />
|
<Header />
|
||||||
|
</Suspense>
|
||||||
{children}
|
{children}
|
||||||
</Providers>
|
</Providers>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { getPostsByUser } from "@lib/server/prisma"
|
||||||
import PostList from "@components/post-list"
|
import PostList from "@components/post-list"
|
||||||
import { getCurrentUser } from "@lib/server/session"
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { authOptions } from "@lib/server/auth"
|
import { authOptions } from "@lib/server/auth"
|
||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
export default async function Mine() {
|
export default async function Mine() {
|
||||||
const userId = (await getCurrentUser())?.id
|
const userId = (await getCurrentUser())?.id
|
||||||
|
@ -15,12 +16,14 @@ export default async function Mine() {
|
||||||
|
|
||||||
const stringifiedPosts = JSON.stringify(posts)
|
const stringifiedPosts = JSON.stringify(posts)
|
||||||
return (
|
return (
|
||||||
|
<Suspense fallback={<PostList skeleton={true} initialPosts={[]} />}>
|
||||||
<PostList
|
<PostList
|
||||||
userId={userId}
|
userId={userId}
|
||||||
initialPosts={stringifiedPosts}
|
initialPosts={stringifiedPosts}
|
||||||
isOwner={true}
|
isOwner={true}
|
||||||
hideSearch={false}
|
hideSearch={false}
|
||||||
/>
|
/>
|
||||||
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
119
src/app/page.tsx
119
src/app/page.tsx
|
@ -2,17 +2,72 @@ import Image from "next/image"
|
||||||
import Card from "@components/card"
|
import Card from "@components/card"
|
||||||
import { getWelcomeContent } from "src/pages/api/welcome"
|
import { getWelcomeContent } from "src/pages/api/welcome"
|
||||||
import DocumentTabs from "./(posts)/components/tabs"
|
import DocumentTabs from "./(posts)/components/tabs"
|
||||||
import { getAllPosts, Post } from "@lib/server/prisma"
|
import { getAllPosts } from "@lib/server/prisma"
|
||||||
import PostList, { NoPostsFound } from "@components/post-list"
|
import PostList, { NoPostsFound } from "@components/post-list"
|
||||||
import { Suspense } from "react"
|
import { cache, Suspense } from "react"
|
||||||
|
import ErrorBoundary from "@components/error/fallback"
|
||||||
|
import { Stack } from "@components/stack"
|
||||||
|
|
||||||
export async function getWelcomeData() {
|
const getWelcomeData = cache(async () => {
|
||||||
const welcomeContent = await getWelcomeContent()
|
const welcomeContent = await getWelcomeContent()
|
||||||
return welcomeContent
|
return welcomeContent
|
||||||
}
|
})
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const getPostsPromise = getAllPosts({
|
const { title } = await getWelcomeData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction="column">
|
||||||
|
<Stack direction="row" alignItems="center">
|
||||||
|
<Image
|
||||||
|
src={"/assets/logo.svg"}
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
alt=""
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<h1 style={{ marginLeft: "var(--gap)" }}>{title}</h1>
|
||||||
|
</Stack>
|
||||||
|
{/* @ts-expect-error because of async RSC */}
|
||||||
|
<WelcomePost />
|
||||||
|
<h2>Recent public posts</h2>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<PostList
|
||||||
|
skeleton
|
||||||
|
hideActions
|
||||||
|
hideSearch
|
||||||
|
initialPosts={JSON.stringify({})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{/* @ts-expect-error because of async RSC */}
|
||||||
|
<PublicPostList />
|
||||||
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function WelcomePost() {
|
||||||
|
const { content, rendered, title } = await getWelcomeData()
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<DocumentTabs
|
||||||
|
defaultTab="preview"
|
||||||
|
isEditing={false}
|
||||||
|
staticPreview={rendered as string}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</DocumentTabs>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function PublicPostList() {
|
||||||
|
const posts = await getAllPosts({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
title: true,
|
title: true,
|
||||||
|
@ -38,55 +93,6 @@ export default async function Page() {
|
||||||
createdAt: "desc"
|
createdAt: "desc"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { content, rendered, title } = await getWelcomeData()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{ display: "flex", flexDirection: "column", gap: "var(--gap)" }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={"/assets/logo.svg"}
|
|
||||||
width={48}
|
|
||||||
height={48}
|
|
||||||
alt=""
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<h1 style={{ marginLeft: "var(--gap)" }}>{title}</h1>
|
|
||||||
</div>
|
|
||||||
<Card>
|
|
||||||
<DocumentTabs
|
|
||||||
defaultTab="preview"
|
|
||||||
isEditing={false}
|
|
||||||
content={content}
|
|
||||||
preview={rendered as string}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
<div>
|
|
||||||
<h2>Recent public posts</h2>
|
|
||||||
<Suspense
|
|
||||||
fallback={
|
|
||||||
<PostList skeleton hideSearch initialPosts={JSON.stringify({})} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{/* @ts-expect-error because of async RSC */}
|
|
||||||
<PublicPostList getPostsPromise={getPostsPromise} />
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function PublicPostList({
|
|
||||||
getPostsPromise
|
|
||||||
}: {
|
|
||||||
getPostsPromise: Promise<Post[]>
|
|
||||||
}) {
|
|
||||||
try {
|
|
||||||
const posts = await getPostsPromise
|
|
||||||
|
|
||||||
if (posts.length === 0) {
|
if (posts.length === 0) {
|
||||||
return <NoPostsFound />
|
return <NoPostsFound />
|
||||||
|
@ -95,9 +101,4 @@ async function PublicPostList({
|
||||||
return (
|
return (
|
||||||
<PostList initialPosts={JSON.stringify(posts)} hideActions hideSearch />
|
<PostList initialPosts={JSON.stringify(posts)} hideActions hideSearch />
|
||||||
)
|
)
|
||||||
} catch (error) {
|
|
||||||
return <NoPostsFound />
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const revalidate = 60
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { User } from "@prisma/client"
|
||||||
|
|
||||||
function Profile() {
|
function Profile() {
|
||||||
const { session } = useSessionSWR()
|
const { session } = useSessionSWR()
|
||||||
console.log(session)
|
|
||||||
const { data: userData } = useSWR<User>(
|
const { data: userData } = useSWR<User>(
|
||||||
session?.user?.id ? `/api/user/${session?.user?.id}` : null
|
session?.user?.id ? `/api/user/${session?.user?.id}` : null
|
||||||
)
|
)
|
||||||
|
@ -104,7 +103,7 @@ function Profile() {
|
||||||
type="email"
|
type="email"
|
||||||
width={"100%"}
|
width={"100%"}
|
||||||
placeholder="my@email.io"
|
placeholder="my@email.io"
|
||||||
value={session?.user.email || undefined}
|
value={session?.user.email || ""}
|
||||||
disabled
|
disabled
|
||||||
aria-label="Email"
|
aria-label="Email"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -49,7 +49,6 @@ const providers = () => {
|
||||||
// @ts-expect-error TODO: fix types
|
// @ts-expect-error TODO: fix types
|
||||||
credentials: credentialsOptions() as unknown,
|
credentials: credentialsOptions() as unknown,
|
||||||
async authorize(credentials) {
|
async authorize(credentials) {
|
||||||
console.log("credentials")
|
|
||||||
if (!credentials || !credentials.username || !credentials.password) {
|
if (!credentials || !credentials.username || !credentials.password) {
|
||||||
throw new Error("Missing credentials")
|
throw new Error("Missing credentials")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { withMethods } from "@lib/api-middleware/with-methods"
|
||||||
|
|
||||||
const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => {
|
const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const { id, download } = req.query
|
const { id, download } = req.query
|
||||||
|
|
||||||
const file = await prisma.file.findUnique({
|
const file = await prisma.file.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: parseQueryParam(id)
|
id: parseQueryParam(id)
|
||||||
|
|
Loading…
Reference in a new issue