Remove geist-ui, add loading prop to button, convert header to CSS

This commit is contained in:
Max Leiter 2022-11-29 22:10:51 -08:00
parent fc79f7df4d
commit ce0c442273
33 changed files with 253 additions and 392 deletions

View file

@ -36,12 +36,15 @@
background-color: var(--lighter-gray); background-color: var(--lighter-gray);
} }
.content li a { .content .listItem {
display: flex; display: flex;
align-items: center; align-items: center;
text-align: center; justify-content: space-between;
padding: var(--gap-half) var(--gap);
color: var(--dark-gray); color: var(--dark-gray);
text-decoration: none;
/* vertical alignment */
padding: var(--gap-quarter) 0;
} }
.button { .button {

View file

@ -1,6 +1,4 @@
import Button from "@components/button"
import { Popover } from "@components/popover" import { Popover } from "@components/popover"
import ShiftBy from "@components/shift-by"
import { codeFileExtensions } from "@lib/constants" import { codeFileExtensions } from "@lib/constants"
import clsx from "clsx" import clsx from "clsx"
import type { File } from "lib/server/prisma" import type { File } from "lib/server/prisma"
@ -32,10 +30,8 @@ const FileDropdown = ({ files }: { files: File[] }) => {
<ul className={styles.content}> <ul className={styles.content}>
{items.map((item) => ( {items.map((item) => (
<li key={item.id}> <li key={item.id}>
<a href={`#${item.title}`}> <a href={`#${item.title}`} className={styles.listItem}>
<ShiftBy y={5}> <span className={styles.fileIcon}>{item.icon}</span>
<span className={styles.fileIcon}>{item.icon}</span>
</ShiftBy>
<span className={styles.fileTitle}> <span className={styles.fileTitle}>
{item.title ? item.title : "Untitled"} {item.title ? item.title : "Untitled"}
</span> </span>
@ -58,7 +54,9 @@ const FileDropdown = ({ files }: { files: File[] }) => {
Jump to {files.length} {files.length === 1 ? "file" : "files"} Jump to {files.length} {files.length === 1 ? "file" : "files"}
</span> </span>
</Popover.Trigger> </Popover.Trigger>
<Popover.Content className={styles.contentWrapper}>{content}</Popover.Content> <Popover.Content className={styles.contentWrapper}>
{content}
</Popover.Content>
</Popover> </Popover>
) )
} }

View file

@ -17,6 +17,7 @@ import Button from "@components/button"
import Input from "@components/input" import Input from "@components/input"
import ButtonDropdown from "@components/button-dropdown" import ButtonDropdown from "@components/button-dropdown"
import { useToasts } from "@components/toasts" import { useToasts } from "@components/toasts"
const emptyDoc = { const emptyDoc = {
title: "", title: "",
content: "", content: "",
@ -30,19 +31,20 @@ export type Document = {
} }
const Post = ({ const Post = ({
initialPost, initialPost: stringifiedInitialPost,
newPostParent newPostParent
}: { }: {
initialPost?: PostWithFiles initialPost?: string
newPostParent?: string newPostParent?: string
}) => { }) => {
const initialPost = JSON.parse(stringifiedInitialPost || "{}") as PostWithFiles
const { setToast } = useToasts() const { setToast } = useToasts()
const router = useRouter() const router = useRouter()
const [title, setTitle] = useState( const [title, setTitle] = useState(
getTitleForPostCopy(initialPost?.title) || "" getTitleForPostCopy(initialPost?.title) || ""
) )
const [description, setDescription] = useState(initialPost?.description || "") const [description, setDescription] = useState(initialPost?.description || "")
const [expiresAt, setExpiresAt] = useState(initialPost?.expiresAt) const [expiresAt, setExpiresAt] = useState<Date>()
const defaultDocs: Document[] = initialPost const defaultDocs: Document[] = initialPost
? initialPost.files?.map((doc) => ({ ? initialPost.files?.map((doc) => ({
@ -212,16 +214,6 @@ const Post = ({
else setDocs((docs) => [...docs, ...files]) else setDocs((docs) => [...docs, ...files])
} }
// pasted files
// const files = e.clipboardData.files as File[]
// if (files.length) {
// const docs = Array.from(files).map((file) => ({
// title: file.name,
// content: '',
// id: generateUUID()
// }))
// }
const onPaste = (e: ClipboardEvent) => { const onPaste = (e: ClipboardEvent) => {
const pastedText = e.clipboardData?.getData("text") const pastedText = e.clipboardData?.getData("text")
@ -259,8 +251,7 @@ const Post = ({
) )
return ( return (
// 150 so the post dropdown doesn't overflow <div style={{ paddingBottom: 200 }}>
<div style={{ paddingBottom: 150 }}>
<Title title={title} onChange={onChangeTitle} /> <Title title={title} onChange={onChangeTitle} />
<Description description={description} onChange={onChangeDescription} /> <Description description={description} onChange={onChangeDescription} />
<FileDropzone setDocs={uploadDocs} /> <FileDropzone setDocs={uploadDocs} />
@ -315,6 +306,7 @@ const Post = ({
height={40} height={40}
width={251} width={251}
onClick={() => onSubmit("unlisted")} onClick={() => onSubmit("unlisted")}
loading={isSubmitting}
> >
Create Unlisted Create Unlisted
</Button> </Button>

View file

@ -25,8 +25,10 @@ const NewFromExisting = async ({
withFiles: true, withFiles: true,
withAuthor: false withAuthor: false
}) })
const serialized = JSON.stringify(post)
return <NewPost initialPost={post} newPostParent={id} /> return <NewPost initialPost={serialized} newPostParent={id} />
} }
export default NewFromExisting export default NewFromExisting

View file

@ -27,6 +27,7 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
const [post, setPost] = useState<PostWithFilesAndAuthor>( const [post, setPost] = useState<PostWithFilesAndAuthor>(
typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost typeof initialPost === "string" ? JSON.parse(initialPost) : initialPost
) )
const [visibility, setVisibility] = useState<string>(post.visibility) const [visibility, setVisibility] = useState<string>(post.visibility)
const router = useRouter() const router = useRouter()
@ -63,6 +64,10 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
} }
}, [isAuthor, post.expiresAt, router]) }, [isAuthor, post.expiresAt, router])
if (isProtected) {
return <PasswordModalPage setPost={setPost} postId={post.id} />
}
const download = async () => { const download = async () => {
if (!post.files) return if (!post.files) return
const downloadZip = (await import("client-zip")).downloadZip const downloadZip = (await import("client-zip")).downloadZip
@ -89,12 +94,9 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
const viewParentClick = () => { const viewParentClick = () => {
router.push(`/post/${post.parentId}`) router.push(`/post/${post.parentId}`)
} }
const isAvailable = !isProtected && post.title
return ( return (
<> <>
{!isAvailable && <PasswordModalPage setPost={setPost} postId={post.id} />}
<div className={styles.header}> <div className={styles.header}>
<span className={styles.buttons}> <span className={styles.buttons}>
<ButtonGroup verticalIfMobile> <ButtonGroup verticalIfMobile>

View file

@ -30,14 +30,19 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<Button iconRight={<Download />} aria-label="Download" /> <Button
iconRight={<Download />}
aria-label="Download"
style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
/>
</Link> </Link>
</Tooltip> </Tooltip>
<Tooltip content="Open raw in new tab"> <Tooltip content="Open raw in new tab">
<Link href={rawLink || ""} target="_blank" rel="noopener noreferrer"> <Link href={rawLink || ""} target="_blank" rel="noopener noreferrer">
<Button <Button
iconRight={<ExternalLink />} iconLeft={<ExternalLink />}
aria-label="Open raw file in new tab" aria-label="Open raw file in new tab"
style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}
/> />
</Link> </Link>
</Tooltip> </Tooltip>

View file

@ -16,7 +16,7 @@ export default async function Head({
return ( return (
<PageSeo <PageSeo
title={`${post.title} - Drift`} title={post.title}
description={post.description || undefined} description={post.description || undefined}
isPrivate={false} isPrivate={false}
/> />

View file

@ -47,7 +47,10 @@ const getPost = async (id: string) => {
if (post.visibility === "protected" && !isAuthorOrAdmin) { if (post.visibility === "protected" && !isAuthorOrAdmin) {
return { return {
// post, post: {
visibility: "protected",
id: post.id
},
isProtected: true, isProtected: true,
isAuthor: isAuthorOrAdmin isAuthor: isAuthorOrAdmin
} }

View file

@ -1,4 +1,5 @@
import Tooltip from "@components/tooltip" 'use client'
// import Tooltip from "@components/tooltip"
import { timeAgo } from "@lib/time-ago" import { timeAgo } from "@lib/time-ago"
import { useMemo, useState, useEffect } from "react" import { useMemo, useState, useEffect } from "react"
import Badge from "../badge" import Badge from "../badge"
@ -14,14 +15,15 @@ const CreatedAgoBadge = ({ createdAt }: { createdAt: string | Date }) => {
return () => clearInterval(interval) return () => clearInterval(interval)
}, [createdDate]) }, [createdDate])
const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}` // const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}`
return ( return (
<Badge type="secondary"> // TODO: investigate tooltip
{" "} // <Tooltip content={formattedTime}>
<Tooltip content={formattedTime}> <Badge type="secondary">
{" "}
<>{time}</> <>{time}</>
</Tooltip> </Badge>
</Badge> // </Tooltip>
) )
} }

View file

@ -12,7 +12,7 @@ type Props = {
} }
const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => { const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
const [isSubmitting, setSubmitting] = useState(false) const [isSubmitting, setSubmitting] = useState<string | null>()
const [passwordModalVisible, setPasswordModalVisible] = useState(false) const [passwordModalVisible, setPasswordModalVisible] = useState(false)
const { setToast } = useToasts() const { setToast } = useToasts()
@ -47,53 +47,53 @@ const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
return return
} }
setPasswordModalVisible(false) setPasswordModalVisible(false)
const timeout = setTimeout(() => setSubmitting(true), 100) const timeout = setTimeout(() => setSubmitting(visibility), 100)
await sendRequest(visibility, password) await sendRequest(visibility, password)
clearTimeout(timeout) clearTimeout(timeout)
setSubmitting(false) setSubmitting(null)
}, },
[sendRequest] [sendRequest]
) )
const onClosePasswordModal = () => { const onClosePasswordModal = () => {
setPasswordModalVisible(false) setPasswordModalVisible(false)
setSubmitting(false) setSubmitting(null)
} }
const submitPassword = (password: string) => onSubmit("protected", password) const submitPassword = (password: string) => onSubmit("protected", password)
return ( return (
<> <>
{isSubmitting ? ( <ButtonGroup verticalIfMobile>
<Spinner /> <Button
) : ( disabled={visibility === "private"}
<ButtonGroup verticalIfMobile> onClick={() => onSubmit("private")}
<Button >
disabled={visibility === "private"} {isSubmitting === "private" ? <Spinner /> : "Make Private"}
onClick={() => onSubmit("private")} </Button>
> <Button
Make Private disabled={visibility === "public"}
</Button> onClick={() => onSubmit("public")}
<Button >
disabled={visibility === "public"} {isSubmitting === "public" ? <Spinner /> : "Make Public"}
onClick={() => onSubmit("public")} </Button>
> <Button
Make Public disabled={visibility === "unlisted"}
</Button> onClick={() => onSubmit("unlisted")}
<Button >
disabled={visibility === "unlisted"} {isSubmitting === "unlisted" ? <Spinner /> : "Make Unlisted"}
onClick={() => onSubmit("unlisted")} </Button>
> <Button onClick={() => onSubmit("protected")}>
Unlist {isSubmitting === "protected" ? (
</Button> <Spinner />
<Button onClick={() => onSubmit("protected")}> ) : visibility === "protected" ? (
{visibility === "protected" "Change Password"
? "Change Password" ) : (
: "Protect with Password"} "Protect with Password"
</Button> )}
</ButtonGroup> </Button>
)} </ButtonGroup>
<PasswordModal <PasswordModal
creating={true} creating={true}
isOpen={passwordModalVisible} isOpen={passwordModalVisible}

View file

@ -26,7 +26,7 @@
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.verticalIfMobile { .verticalIfMobile {
flex-direction: column; flex-direction: column !important;
} }
.verticalIfMobile.button-group > button:first-of-type { .verticalIfMobile.button-group > button:first-of-type {

View file

@ -11,9 +11,9 @@ export default function ButtonGroup({
return ( return (
<div <div
className={clsx( className={clsx(
props.className,
styles["button-group"], styles["button-group"],
verticalIfMobile && styles.verticalIfMobile verticalIfMobile && styles.verticalIfMobile,
props.className
)} )}
{...props} {...props}
> >

View file

@ -1,6 +1,7 @@
import styles from "./button.module.css" import styles from "./button.module.css"
import { forwardRef, Ref } from "react" import { forwardRef, Ref } from "react"
import clsx from "clsx" import clsx from "clsx"
import { Spinner } from "@components/spinner"
type Props = React.HTMLProps<HTMLButtonElement> & { type Props = React.HTMLProps<HTMLButtonElement> & {
children?: React.ReactNode children?: React.ReactNode
@ -13,6 +14,7 @@ type Props = React.HTMLProps<HTMLButtonElement> & {
width?: string | number width?: string | number
padding?: string | number padding?: string | number
margin?: string | number margin?: string | number
loading?: boolean
} }
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
@ -31,6 +33,7 @@ const Button = forwardRef<HTMLButtonElement, Props>(
width, width,
padding, padding,
margin, margin,
loading,
...props ...props
}, },
ref ref
@ -39,7 +42,7 @@ const Button = forwardRef<HTMLButtonElement, Props>(
<button <button
ref={ref} ref={ref}
className={`${styles.button} ${styles[type]} ${className || ""}`} className={`${styles.button} ${styles[type]} ${className || ""}`}
disabled={disabled} disabled={disabled || loading}
onClick={onClick} onClick={onClick}
style={{ height, width, margin, padding }} style={{ height, width, margin, padding }}
{...props} {...props}
@ -47,10 +50,22 @@ const Button = forwardRef<HTMLButtonElement, Props>(
{children && iconLeft && ( {children && iconLeft && (
<span className={clsx(styles.icon, styles.iconLeft)}>{iconLeft}</span> <span className={clsx(styles.icon, styles.iconLeft)}>{iconLeft}</span>
)} )}
{children ? ( {!loading &&
children (children ? (
) : ( children
<span className={styles.icon}>{iconLeft || iconRight}</span> ) : (
<span className={styles.icon}>{iconLeft || iconRight}</span>
))}
{loading && (
<span
style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
<Spinner />
</span>
)} )}
{children && iconRight && ( {children && iconRight && (
<span className={clsx(styles.icon, styles.iconRight)}> <span className={clsx(styles.icon, styles.iconRight)}>

View file

@ -30,44 +30,6 @@
box-shadow: inset 0 -1px 0 var(--fg); box-shadow: inset 0 -1px 0 var(--fg);
} }
.mobile {
position: absolute;
z-index: 1000;
display: flex;
flex-direction: column;
background: var(--bg);
width: 100%;
height: 100%;
top: 70px;
left: 0;
}
.mobile button {
z-index: 1000;
width: 100%;
}
.controls {
margin-top: var(--gap);
display: none !important;
}
@media only screen and (max-width: 650px) {
.tabs {
display: none;
}
.controls {
display: block !important;
}
}
.controls button:active,
.controls button:focus,
.controls button:hover {
outline: 1px solid rgba(0, 0, 0, 0.2);
}
.wrapper { .wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
@ -86,3 +48,33 @@
.active button { .active button {
color: var(--fg); color: var(--fg);
} }
.buttonGroup,
.mobile {
display: none;
}
@media only screen and (max-width: 768px) {
.wrapper [data-tab="github"] {
display: none;
}
.mobile {
margin-top: var(--gap);
display: flex;
}
.buttonGroup {
display: flex;
flex-direction: column;
}
.dropdownItem a,
.dropdownItem button {
width: 100%;
}
.tabs {
display: none;
}
}

View file

@ -1,11 +1,8 @@
"use client" "use client"
import { useBodyScroll, useMediaQuery } from "@geist-ui/core/dist"
import { useEffect, useState } from "react"
import styles from "./header.module.css" import styles from "./header.module.css"
// import useUserData from "@lib/hooks/use-user-data" // import useUserData from "@lib/hooks/use-user-data"
import Link from "next/link" import Link from "@components/link"
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { signOut } from "next-auth/react" import { signOut } from "next-auth/react"
import Button from "@components/button" import Button from "@components/button"
@ -23,6 +20,11 @@ import {
UserPlus, UserPlus,
UserX UserX
} from "react-feather" } from "react-feather"
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import buttonStyles from "@components/button/button.module.css"
import ButtonGroup from "@components/button-group"
import { useEffect, useMemo, useState } from "react"
import Skeleton from "@components/skeleton"
type Tab = { type Tab = {
name: string name: string
@ -34,38 +36,52 @@ type Tab = {
const Header = ({ signedIn = false, isAdmin = false }) => { const Header = ({ signedIn = false, isAdmin = false }) => {
const pathname = usePathname() const pathname = usePathname()
const [expanded, setExpanded] = useState<boolean>(false) // wait to mount before rendering
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) const [isHydrated, setHydrated] = useState(false)
const isMobile = useMediaQuery("xs", { match: "down" }) const { setTheme, resolvedTheme } = useTheme()
// const { status } = useSession()
// const signedIn = status === "authenticated"
const { setTheme, theme } = useTheme()
useEffect(() => {
setBodyHidden(expanded)
}, [expanded, setBodyHidden])
useEffect(() => { useEffect(() => {
if (!isMobile) { setHydrated(true)
setExpanded(false) }, [])
const getButton = (tab: Tab) => {
const isActive = pathname === tab.href
const activeStyle = isActive ? styles.active : ""
if (tab.onClick) {
return (
<Button
key={tab.value}
iconLeft={tab.icon}
onClick={tab.onClick}
className={clsx(styles.tab, activeStyle)}
aria-label={tab.name}
aria-current={isActive ? "page" : undefined}
data-tab={tab.value}
>
{tab.name ? tab.name : undefined}
</Button>
)
} else if (tab.href) {
return (
<Link
key={tab.value}
href={tab.href}
className={clsx(styles.tab, activeStyle)}
data-tab={tab.value}
>
<Button iconLeft={tab.icon}>{tab.name ? tab.name : undefined}</Button>
</Link>
)
} }
}, [isMobile]) }
const getPages = () => { const pages = useMemo(() => {
const defaultPages: Tab[] = [ const defaultPages: Tab[] = [
{ {
name: isMobile ? "GitHub" : "", name: "GitHub",
href: "https://github.com/maxleiter/drift", href: "https://github.com/maxleiter/drift",
icon: <GitHub />, icon: <GitHub />,
value: "github" value: "github"
},
{
name: isMobile ? "Change theme" : "",
onClick: function () {
if (typeof window !== "undefined")
setTheme(theme === "light" ? "dark" : "light")
},
icon: theme === "light" ? <Moon /> : <Sun />,
value: "theme"
} }
] ]
@ -78,6 +94,15 @@ const Header = ({ signedIn = false, isAdmin = false }) => {
}) })
} }
defaultPages.push({
name: "Theme",
onClick: function () {
setTheme(resolvedTheme === "light" ? "dark" : "light")
},
icon: isHydrated ? (resolvedTheme === "light" ? <Moon /> : <Sun />) : <></>,
value: "theme"
})
if (signedIn) if (signedIn)
return [ return [
{ {
@ -131,46 +156,16 @@ const Header = ({ signedIn = false, isAdmin = false }) => {
}, },
...defaultPages ...defaultPages
] ]
} }, [isAdmin, isHydrated, resolvedTheme, signedIn, setTheme])
const pages = getPages() // // TODO: this should not be necessary.
// if (!clientHydrated) {
const onTabChange = (tab: string) => { // return (
if (typeof window === "undefined") return // <header>
const match = pages.find((page) => page.value === tab) // <div className={styles.tabs}>{getPages(true).map(getButton)}</div>
if (match?.onClick) { // </header>
match.onClick() // )
} // }
}
const getButton = (tab: Tab) => {
const isActive = pathname === tab.href
const activeStyle = isActive ? styles.active : ""
if (tab.onClick) {
return (
<Button
key={tab.value}
iconLeft={tab.icon}
onClick={() => onTabChange(tab.value)}
className={clsx(styles.tab, activeStyle)}
aria-label={tab.name}
aria-current={isActive ? "page" : undefined}
>
{tab.name ? tab.name : undefined}
</Button>
)
} else if (tab.href) {
return (
<Link
key={tab.value}
href={tab.href}
className={clsx(styles.tab, activeStyle)}
>
<Button iconLeft={tab.icon}>{tab.name ? tab.name : undefined}</Button>
</Link>
)
}
}
const buttons = pages.map(getButton) const buttons = pages.map(getButton)
@ -179,17 +174,28 @@ const Header = ({ signedIn = false, isAdmin = false }) => {
<div className={styles.tabs}> <div className={styles.tabs}>
<div className={styles.buttons}>{buttons}</div> <div className={styles.buttons}>{buttons}</div>
</div> </div>
<div className={styles.controls}> <DropdownMenu.Root>
<Button onClick={() => setExpanded(!expanded)} aria-label="Menu"> <DropdownMenu.Trigger
<Menu /> className={clsx(buttonStyles.button, styles.mobile)}
</Button> asChild
</div> >
{/* setExpanded should occur elsewhere; we don't want to close if they change themes */} <Button aria-label="Menu">
{isMobile && expanded && ( <Menu />
<div className={styles.mobile} onClick={() => setExpanded(!expanded)}> </Button>
{buttons} </DropdownMenu.Trigger>
</div> <DropdownMenu.Portal>
)} <DropdownMenu.Content className={styles.contentWrapper}>
{buttons.map((button) => (
<DropdownMenu.Item
key={button?.key}
className={styles.dropdownItem}
>
{button}
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</header> </header>
) )
} }

View file

@ -1,14 +1,16 @@
import clsx from "clsx"
import styles from "./note.module.css" import styles from "./note.module.css"
const Note = ({ const Note = ({
type = "info", type = "info",
children, children,
className,
...props ...props
}: { }: {
type: "info" | "warning" | "error" type: "info" | "warning" | "error"
children: React.ReactNode children: React.ReactNode
} & React.ComponentProps<"div">) => ( } & React.ComponentProps<"div">) => (
<div className={`${styles.note} ${styles[type]}`} {...props}> <div className={clsx(className, styles.note, styles[type])} {...props}>
{children} {children}
</div> </div>
) )

View file

@ -9,13 +9,14 @@ type PageSeoProps = {
} }
const PageSeo = ({ const PageSeo = ({
title = "Drift", title: pageTitle,
description = "A self-hostable clone of GitHub Gist", description = "A self-hostable clone of GitHub Gist",
isPrivate = false isPrivate = false
}: PageSeoProps) => { }: PageSeoProps) => {
const title = `Drift${pageTitle ? ` - ${pageTitle}` : ""}`
return ( return (
<> <>
<title>Drift{title ? ` - ${title}` : ""}</title> <title>{title}</title>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
{!isPrivate && <meta name="description" content={description} />} {!isPrivate && <meta name="description" content={description} />}
{isPrivate && <meta name="robots" content="noindex" />} {isPrivate && <meta name="robots" content="noindex" />}

View file

@ -1,10 +1,10 @@
.page { .page {
max-width: 100vw; max-width: var(--main-content);
min-height: 100vh; min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
width: 100%; width: 100%;
height: auto; height: auto;
padding: 0 calc(1.34 * 16px) 0 calc(1.34 * 16px); padding: 0 calc(1.34 * 16px) 0 calc(1.34 * 16px);
margin: 0 auto 0 auto; margin: 0 auto;
} }

View file

@ -53,7 +53,7 @@ const PasswordModal = ({
onEscapeKeyDown={onClose} onEscapeKeyDown={onClose}
> >
<Dialog.Title> <Dialog.Title>
{creating ? "Create a password" : "Enter password"} {creating ? "Add a password" : "Enter password"}
</Dialog.Title> </Dialog.Title>
<Dialog.Description> <Dialog.Description>
{creating {creating

View file

@ -4,14 +4,16 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0,0,0,0.5); background: rgba(0, 0, 0, 0.5);
background: var(--gray-alpha);
z-index: 1; z-index: 1;
} }
.content { .content {
background-color: var(--bg); background-color: var(--bg);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
@ -20,7 +22,7 @@
max-width: 450px; max-width: 450px;
max-height: 85vh; max-height: 85vh;
padding: 25px; padding: 25px;
animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); animation: contentShow 100ms cubic-bezier(0.16, 1, 0.3, 1);
z-index: 2; z-index: 2;
border: 1px solid var(--border); border: 1px solid var(--border);
} }
@ -31,7 +33,7 @@
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--gap-quarter); gap: var(--gap);
margin-bottom: var(--gap-half); margin-bottom: var(--gap-half);
} }

View file

@ -1,20 +0,0 @@
// https://www.joshwcomeau.com/snippets/react-components/shift-by/
type Props = {
x?: number
y?: number
children: React.ReactNode
}
function ShiftBy({ x = 0, y = 0, children }: Props) {
return (
<div
style={{
transform: `translate(${x}px, ${y}px)`,
display: "inline-block"
}}
>
{children}
</div>
)
}
export default ShiftBy

View file

@ -1,5 +1,5 @@
import PageSeo from "@components/page-seo" import PageSeo from "@components/page-seo"
export default function RootHead() { export default function RootHead() {
return <PageSeo title="Drift" /> return <PageSeo />
} }

View file

@ -1,6 +1,5 @@
import "@styles/globals.css" import "@styles/globals.css"
import { LayoutWrapper } from "./root-layout-wrapper" import { LayoutWrapper } from "./root-layout-wrapper"
import styles from "@styles/Home.module.css"
import { getSession } from "@lib/server/session" import { getSession } from "@lib/server/session"
import { ServerThemeProvider } from "@wits/next-themes" import { ServerThemeProvider } from "@wits/next-themes"
@ -14,7 +13,6 @@ export default async function RootLayout({ children }: RootLayoutProps) {
return ( return (
<ServerThemeProvider <ServerThemeProvider
enableSystem={true} enableSystem={true}
defaultTheme="dark"
disableTransitionOnChange disableTransitionOnChange
cookieName={"drift-theme"} cookieName={"drift-theme"}
attribute="data-theme" attribute="data-theme"
@ -22,7 +20,7 @@ export default async function RootLayout({ children }: RootLayoutProps) {
> >
<html lang="en"> <html lang="en">
<head /> <head />
<body className={styles.main}> <body>
<LayoutWrapper <LayoutWrapper
signedIn={Boolean(session?.user)} signedIn={Boolean(session?.user)}
isAdmin={session?.user.role === "admin"} isAdmin={session?.user.role === "admin"}

View file

@ -1,5 +1,5 @@
import PageSeo from "@components/page-seo" import PageSeo from "@components/page-seo"
export default function Head() { export default function Head() {
return <PageSeo title="Drift - Your profile" isPrivate /> return <PageSeo title="Your profile" isPrivate />
} }

View file

@ -5,7 +5,6 @@ import Page from "@components/page"
import { Toasts } from "@components/toasts" import { Toasts } from "@components/toasts"
import * as RadixTooltip from "@radix-ui/react-tooltip" import * as RadixTooltip from "@radix-ui/react-tooltip"
import { ThemeProvider } from "@wits/next-themes" import { ThemeProvider } from "@wits/next-themes"
import { Toaster } from "react-hot-toast"
export function LayoutWrapper({ export function LayoutWrapper({
children, children,

View file

@ -0,0 +1,7 @@
.form {
display: flex;
flex-direction: column;
gap: var(--gap);
max-width: 300px;
margin-top: var(--gap);
}

View file

@ -6,6 +6,7 @@ import Note from "@components/note"
import { useToasts } from "@components/toasts" import { useToasts } from "@components/toasts"
import { User } from "next-auth" import { User } from "next-auth"
import { useState } from "react" import { useState } from "react"
import styles from "./profile.module.css"
const Profile = ({ user }: { user: User }) => { const Profile = ({ user }: { user: User }) => {
// TODO: make this displayName, requires fetching user from DB as session doesnt have it // TODO: make this displayName, requires fetching user from DB as session doesnt have it
@ -63,15 +64,7 @@ const Profile = ({ user }: { user: User }) => {
<Note type="warning"> <Note type="warning">
This information will be publicly available on your profile This information will be publicly available on your profile
</Note> </Note>
<form <form onSubmit={onSubmit} className={styles.form}>
style={{
display: "flex",
flexDirection: "column",
gap: "var(--gap)",
maxWidth: "300px"
}}
onSubmit={onSubmit}
>
<div> <div>
<label htmlFor="displayName">Display name</label> <label htmlFor="displayName">Display name</label>
<Input <Input
@ -81,6 +74,8 @@ const Profile = ({ user }: { user: User }) => {
value={name || ""} value={name || ""}
onChange={handleNameChange} onChange={handleNameChange}
aria-label="Display name" aria-label="Display name"
minLength={1}
maxLength={32}
/> />
</div> </div>
<div> <div>
@ -95,20 +90,7 @@ const Profile = ({ user }: { user: User }) => {
aria-label="Email" aria-label="Email"
/> />
</div> </div>
{/* <div> <Button type="submit">Submit</Button>
<label htmlFor="bio">Biography (max 250 characters)</label>
<textarea
id="bio"
style={{ width: "100%" }}
maxLength={250}
placeholder="I enjoy..."
value={bio}
onChange={handleBioChange}
/>
</div> */}
<Button type="submit">
Submit
</Button>
</form> </form>
</> </>
) )

View file

@ -1,5 +1,5 @@
import PageSeo from "@components/page-seo" import PageSeo from "@components/page-seo"
export default function Head() { export default function Head() {
return <PageSeo title="Drift - Settings" isPrivate /> return <PageSeo title="Settings" isPrivate />
} }

View file

@ -1,11 +0,0 @@
.wrapper {
height: 100% !important;
padding-bottom: var(--small-gap) !important;
width: 100% !important;
}
.main {
max-width: var(--main-content) !important;
margin: 0 auto !important;
padding: 0 0 !important;
}

View file

@ -44,7 +44,7 @@
--header-bg: rgba(19, 20, 21, 0.45); --header-bg: rgba(19, 20, 21, 0.45);
--gray-alpha: rgba(255, 255, 255, 0.5); --gray-alpha: rgba(255, 255, 255, 0.5);
--selection: rgba(255, 255, 255, 0.99); --selection: rgba(255, 255, 255, 0.99);
--border: var(--lighter-gray); --border: var(--light-gray);
--warning: #ff6700; --warning: #ff6700;
--link: #3291ff; --link: #3291ff;
color-scheme: dark; color-scheme: dark;
@ -62,7 +62,7 @@
--gray: #888; --gray: #888;
--light-gray: #dedede; --light-gray: #dedede;
--lighter-gray: #f5f5f5; --lighter-gray: #f2f2f2;
--lightest-gray: #fafafa; --lightest-gray: #fafafa;
--darker-gray: #555; --darker-gray: #555;
--darkest-gray: #222; --darkest-gray: #222;
@ -80,7 +80,7 @@
// TODO: replace this with an accessible alternative // TODO: replace this with an accessible alternative
*:focus-visible { *:focus-visible {
outline: none; outline: none !important;
} }
::selection { ::selection {

View file

@ -7,7 +7,7 @@ import config from "@lib/config"
const providers: NextAuthOptions["providers"] = [ const providers: NextAuthOptions["providers"] = [
GitHubProvider({ GitHubProvider({
clientId: config.github_client_id, clientId: config.github_client_id,
clientSecret: config.github_client_secret clientSecret: config.github_client_secret,
}) })
] ]

View file

@ -13,7 +13,6 @@
"jest": "jest" "jest": "jest"
}, },
"dependencies": { "dependencies": {
"@geist-ui/core": "^2.3.8",
"@next-auth/prisma-adapter": "^1.0.5", "@next-auth/prisma-adapter": "^1.0.5",
"@next/eslint-plugin-next": "13.0.5-canary.3", "@next/eslint-plugin-next": "13.0.5-canary.3",
"@prisma/client": "^4.6.1", "@prisma/client": "^4.6.1",
@ -26,20 +25,16 @@
"@wits/next-themes": "^0.2.12", "@wits/next-themes": "^0.2.12",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"client-zip": "2.2.1", "client-zip": "2.2.1",
"cookies-next": "^2.1.1",
"jest": "^29.3.1", "jest": "^29.3.1",
"next": "13.0.6-canary.2", "next": "13.0.6-canary.2",
"next-auth": "^4.17.0", "next-auth": "^4.17.0",
"prisma": "^4.6.1", "prisma": "^4.6.1",
"rc-table": "7.24.1",
"react": "18.2.0", "react": "18.2.0",
"react-datepicker": "4.8.0", "react-datepicker": "4.8.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-dropzone": "14.2.3", "react-dropzone": "14.2.3",
"react-feather": "^2.0.10", "react-feather": "^2.0.10",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"server-only": "^0.0.1",
"swr": "1.3.0",
"textarea-markdown-editor": "0.1.13", "textarea-markdown-editor": "0.1.13",
"ts-jest": "^29.0.3" "ts-jest": "^29.0.3"
}, },
@ -54,7 +49,6 @@
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.27.0", "eslint": "8.27.0",
"eslint-config-next": "13.0.3", "eslint-config-next": "13.0.3",
"katex": "^0.16.3",
"next-unused": "0.0.6", "next-unused": "0.0.6",
"prettier": "2.6.2", "prettier": "2.6.2",
"typescript": "4.6.4", "typescript": "4.6.4",

View file

@ -1,7 +1,6 @@
lockfileVersion: 5.4 lockfileVersion: 5.4
specifiers: specifiers:
'@geist-ui/core': ^2.3.8
'@next-auth/prisma-adapter': ^1.0.5 '@next-auth/prisma-adapter': ^1.0.5
'@next/bundle-analyzer': 12.1.6 '@next/bundle-analyzer': 12.1.6
'@next/eslint-plugin-next': 13.0.5-canary.3 '@next/eslint-plugin-next': 13.0.5-canary.3
@ -21,34 +20,28 @@ specifiers:
bcrypt: ^5.1.0 bcrypt: ^5.1.0
client-zip: 2.2.1 client-zip: 2.2.1
clsx: ^1.2.1 clsx: ^1.2.1
cookies-next: ^2.1.1
cross-env: 7.0.3 cross-env: 7.0.3
eslint: 8.27.0 eslint: 8.27.0
eslint-config-next: 13.0.3 eslint-config-next: 13.0.3
jest: ^29.3.1 jest: ^29.3.1
katex: ^0.16.3
next: 13.0.6-canary.2 next: 13.0.6-canary.2
next-auth: ^4.17.0 next-auth: ^4.17.0
next-unused: 0.0.6 next-unused: 0.0.6
prettier: 2.6.2 prettier: 2.6.2
prisma: ^4.6.1 prisma: ^4.6.1
rc-table: 7.24.1
react: 18.2.0 react: 18.2.0
react-datepicker: 4.8.0 react-datepicker: 4.8.0
react-dom: 18.2.0 react-dom: 18.2.0
react-dropzone: 14.2.3 react-dropzone: 14.2.3
react-feather: ^2.0.10 react-feather: ^2.0.10
react-hot-toast: ^2.4.0 react-hot-toast: ^2.4.0
server-only: ^0.0.1
sharp: ^0.31.2 sharp: ^0.31.2
swr: 1.3.0
textarea-markdown-editor: 0.1.13 textarea-markdown-editor: 0.1.13
ts-jest: ^29.0.3 ts-jest: ^29.0.3
typescript: 4.6.4 typescript: 4.6.4
typescript-plugin-css-modules: 3.4.0 typescript-plugin-css-modules: 3.4.0
dependencies: dependencies:
'@geist-ui/core': 2.3.8_biqbaboplfbrettd7655fr4n2y
'@next-auth/prisma-adapter': 1.0.5_o53gfpk3vz2btjrokqfjjwwn3m '@next-auth/prisma-adapter': 1.0.5_o53gfpk3vz2btjrokqfjjwwn3m
'@next/eslint-plugin-next': 13.0.5-canary.3 '@next/eslint-plugin-next': 13.0.5-canary.3
'@prisma/client': 4.6.1_prisma@4.6.1 '@prisma/client': 4.6.1_prisma@4.6.1
@ -61,20 +54,16 @@ dependencies:
'@wits/next-themes': 0.2.12_hzq4dmqplfkom7c35ucps6atz4 '@wits/next-themes': 0.2.12_hzq4dmqplfkom7c35ucps6atz4
bcrypt: 5.1.0 bcrypt: 5.1.0
client-zip: 2.2.1 client-zip: 2.2.1
cookies-next: 2.1.1
jest: 29.3.1_@types+node@17.0.23 jest: 29.3.1_@types+node@17.0.23
next: 13.0.6-canary.2_biqbaboplfbrettd7655fr4n2y next: 13.0.6-canary.2_biqbaboplfbrettd7655fr4n2y
next-auth: 4.17.0_hzq4dmqplfkom7c35ucps6atz4 next-auth: 4.17.0_hzq4dmqplfkom7c35ucps6atz4
prisma: 4.6.1 prisma: 4.6.1
rc-table: 7.24.1_biqbaboplfbrettd7655fr4n2y
react: 18.2.0 react: 18.2.0
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
react-dropzone: 14.2.3_react@18.2.0 react-dropzone: 14.2.3_react@18.2.0
react-feather: 2.0.10_react@18.2.0 react-feather: 2.0.10_react@18.2.0
react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y
server-only: 0.0.1
swr: 1.3.0_react@18.2.0
textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y
ts-jest: 29.0.3_7hcmezpa7bajbjecov7p46z4aa ts-jest: 29.0.3_7hcmezpa7bajbjecov7p46z4aa
@ -92,7 +81,6 @@ devDependencies:
cross-env: 7.0.3 cross-env: 7.0.3
eslint: 8.27.0 eslint: 8.27.0
eslint-config-next: 13.0.3_hsmo2rtalirsvadpuxki35bq2i eslint-config-next: 13.0.3_hsmo2rtalirsvadpuxki35bq2i
katex: 0.16.3
next-unused: 0.0.6 next-unused: 0.0.6
prettier: 2.6.2 prettier: 2.6.2
typescript: 4.6.4 typescript: 4.6.4
@ -490,17 +478,6 @@ packages:
- '@types/react' - '@types/react'
dev: false dev: false
/@geist-ui/core/2.3.8_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-OKwGgTA4+fBM41eQbqDoUj4XBycZbYH7Ynrn6LPO5yKX7zeWPu/R7HN3vB4/oHt34VTDQI5sDNb1SirHvNyB5w==}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
'@babel/runtime': 7.20.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/@humanwhocodes/config-array/0.11.7: /@humanwhocodes/config-array/0.11.7:
resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==} resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@ -1498,10 +1475,6 @@ packages:
'@types/node': 17.0.23 '@types/node': 17.0.23
dev: true dev: true
/@types/cookie/0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
dev: false
/@types/debug/4.1.7: /@types/debug/4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies: dependencies:
@ -1554,10 +1527,6 @@ packages:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: false dev: false
/@types/node/16.18.3:
resolution: {integrity: sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==}
dev: false
/@types/node/17.0.23: /@types/node/17.0.23:
resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==} resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==}
@ -2320,6 +2289,7 @@ packages:
/commander/8.3.0: /commander/8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'} engines: {node: '>= 12'}
dev: false
/commondir/1.0.1: /commondir/1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
@ -2340,24 +2310,11 @@ packages:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: false dev: false
/cookie/0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'}
dev: false
/cookie/0.5.0: /cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/cookies-next/2.1.1:
resolution: {integrity: sha512-AZGZPdL1hU3jCjN2UMJTGhLOYzNUN9Gm+v8BdptYIHUdwz397Et1p+sZRfvAl8pKnnmMdX2Pk9xDRKCGBum6GA==}
dependencies:
'@types/cookie': 0.4.1
'@types/node': 16.18.3
cookie: 0.4.2
dev: false
/copy-anything/2.0.6: /copy-anything/2.0.6:
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
dependencies: dependencies:
@ -4517,13 +4474,6 @@ packages:
commander: 8.3.0 commander: 8.3.0
dev: false dev: false
/katex/0.16.3:
resolution: {integrity: sha512-3EykQddareoRmbtNiNEDgl3IGjryyrp2eg/25fHDEnlHymIDi33bptkMv6K4EOC2LZCybLW/ZkEo6Le+EM9pmA==}
hasBin: true
dependencies:
commander: 8.3.0
dev: true
/kleur/3.0.3: /kleur/3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -5918,49 +5868,6 @@ packages:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
/rc-resize-observer/1.2.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
'@babel/runtime': 7.20.1
classnames: 2.3.2
rc-util: 5.24.4_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
resize-observer-polyfill: 1.5.1
dev: false
/rc-table/7.24.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-DRWpv5z5pmOaTmy5GqWoskeV1thaOu5HuD+2f61b/CkbBqlgJR3cygc5R/Qvd2uVW6pHU0lYulhmz0VLVFm+rw==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
'@babel/runtime': 7.20.1
classnames: 2.3.2
rc-resize-observer: 1.2.0_biqbaboplfbrettd7655fr4n2y
rc-util: 5.24.4_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
shallowequal: 1.1.0
dev: false
/rc-util/5.24.4_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
'@babel/runtime': 7.20.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-is: 16.13.1
shallowequal: 1.1.0
dev: false
/rc/1.2.8: /rc/1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true hasBin: true
@ -6314,10 +6221,6 @@ packages:
resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==} resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==}
dev: true dev: true
/resize-observer-polyfill/1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
dev: false
/resolve-cwd/3.0.0: /resolve-cwd/3.0.0:
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -6462,18 +6365,10 @@ packages:
dependencies: dependencies:
lru-cache: 6.0.0 lru-cache: 6.0.0
/server-only/0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
dev: false
/set-blocking/2.0.0: /set-blocking/2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: false dev: false
/shallowequal/1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
dev: false
/sharp/0.31.2: /sharp/0.31.2:
resolution: {integrity: sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==} resolution: {integrity: sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==}
engines: {node: '>=14.15.0'} engines: {node: '>=14.15.0'}
@ -6775,14 +6670,6 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
/swr/1.3.0_react@18.2.0:
resolution: {integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/tapable/1.1.3: /tapable/1.1.3:
resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==}
engines: {node: '>=6'} engines: {node: '>=6'}