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);
}
.content li a {
.content .listItem {
display: flex;
align-items: center;
text-align: center;
padding: var(--gap-half) var(--gap);
justify-content: space-between;
color: var(--dark-gray);
text-decoration: none;
/* vertical alignment */
padding: var(--gap-quarter) 0;
}
.button {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -47,7 +47,10 @@ const getPost = async (id: string) => {
if (post.visibility === "protected" && !isAuthorOrAdmin) {
return {
// post,
post: {
visibility: "protected",
id: post.id
},
isProtected: true,
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 { useMemo, useState, useEffect } from "react"
import Badge from "../badge"
@ -14,14 +15,15 @@ const CreatedAgoBadge = ({ createdAt }: { createdAt: string | Date }) => {
return () => clearInterval(interval)
}, [createdDate])
const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}`
// const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}`
return (
<Badge type="secondary">
{" "}
<Tooltip content={formattedTime}>
// TODO: investigate tooltip
// <Tooltip content={formattedTime}>
<Badge type="secondary">
{" "}
<>{time}</>
</Tooltip>
</Badge>
</Badge>
// </Tooltip>
)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -30,44 +30,6 @@
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 {
display: flex;
align-items: center;
@ -86,3 +48,33 @@
.active button {
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"
import { useBodyScroll, useMediaQuery } from "@geist-ui/core/dist"
import { useEffect, useState } from "react"
import styles from "./header.module.css"
// import useUserData from "@lib/hooks/use-user-data"
import Link from "next/link"
import Link from "@components/link"
import { usePathname } from "next/navigation"
import { signOut } from "next-auth/react"
import Button from "@components/button"
@ -23,6 +20,11 @@ import {
UserPlus,
UserX
} 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 = {
name: string
@ -34,38 +36,52 @@ type Tab = {
const Header = ({ signedIn = false, isAdmin = false }) => {
const pathname = usePathname()
const [expanded, setExpanded] = useState<boolean>(false)
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
const isMobile = useMediaQuery("xs", { match: "down" })
// const { status } = useSession()
// const signedIn = status === "authenticated"
const { setTheme, theme } = useTheme()
useEffect(() => {
setBodyHidden(expanded)
}, [expanded, setBodyHidden])
// wait to mount before rendering
const [isHydrated, setHydrated] = useState(false)
const { setTheme, resolvedTheme } = useTheme()
useEffect(() => {
if (!isMobile) {
setExpanded(false)
setHydrated(true)
}, [])
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[] = [
{
name: isMobile ? "GitHub" : "",
name: "GitHub",
href: "https://github.com/maxleiter/drift",
icon: <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)
return [
{
@ -131,46 +156,16 @@ const Header = ({ signedIn = false, isAdmin = false }) => {
},
...defaultPages
]
}
}, [isAdmin, isHydrated, resolvedTheme, signedIn, setTheme])
const pages = getPages()
const onTabChange = (tab: string) => {
if (typeof window === "undefined") return
const match = pages.find((page) => page.value === tab)
if (match?.onClick) {
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>
)
}
}
// // TODO: this should not be necessary.
// if (!clientHydrated) {
// return (
// <header>
// <div className={styles.tabs}>{getPages(true).map(getButton)}</div>
// </header>
// )
// }
const buttons = pages.map(getButton)
@ -179,17 +174,28 @@ const Header = ({ signedIn = false, isAdmin = false }) => {
<div className={styles.tabs}>
<div className={styles.buttons}>{buttons}</div>
</div>
<div className={styles.controls}>
<Button onClick={() => setExpanded(!expanded)} aria-label="Menu">
<Menu />
</Button>
</div>
{/* setExpanded should occur elsewhere; we don't want to close if they change themes */}
{isMobile && expanded && (
<div className={styles.mobile} onClick={() => setExpanded(!expanded)}>
{buttons}
</div>
)}
<DropdownMenu.Root>
<DropdownMenu.Trigger
className={clsx(buttonStyles.button, styles.mobile)}
asChild
>
<Button aria-label="Menu">
<Menu />
</Button>
</DropdownMenu.Trigger>
<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>
)
}

View file

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

View file

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

View file

@ -1,10 +1,10 @@
.page {
max-width: 100vw;
max-width: var(--main-content);
min-height: 100vh;
box-sizing: border-box;
position: relative;
width: 100%;
height: auto;
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}
>
<Dialog.Title>
{creating ? "Create a password" : "Enter password"}
{creating ? "Add a password" : "Enter password"}
</Dialog.Title>
<Dialog.Description>
{creating

View file

@ -4,14 +4,16 @@
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
background: rgba(0, 0, 0, 0.5);
background: var(--gray-alpha);
z-index: 1;
}
.content {
background-color: var(--bg);
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;
top: 50%;
left: 50%;
@ -20,7 +22,7 @@
max-width: 450px;
max-height: 85vh;
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;
border: 1px solid var(--border);
}
@ -31,7 +33,7 @@
margin: 0;
display: flex;
flex-direction: column;
gap: var(--gap-quarter);
gap: var(--gap);
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"
export default function RootHead() {
return <PageSeo title="Drift" />
return <PageSeo />
}

View file

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

View file

@ -1,5 +1,5 @@
import PageSeo from "@components/page-seo"
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 * as RadixTooltip from "@radix-ui/react-tooltip"
import { ThemeProvider } from "@wits/next-themes"
import { Toaster } from "react-hot-toast"
export function LayoutWrapper({
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 { User } from "next-auth"
import { useState } from "react"
import styles from "./profile.module.css"
const Profile = ({ user }: { user: User }) => {
// 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">
This information will be publicly available on your profile
</Note>
<form
style={{
display: "flex",
flexDirection: "column",
gap: "var(--gap)",
maxWidth: "300px"
}}
onSubmit={onSubmit}
>
<form onSubmit={onSubmit} className={styles.form}>
<div>
<label htmlFor="displayName">Display name</label>
<Input
@ -81,6 +74,8 @@ const Profile = ({ user }: { user: User }) => {
value={name || ""}
onChange={handleNameChange}
aria-label="Display name"
minLength={1}
maxLength={32}
/>
</div>
<div>
@ -95,20 +90,7 @@ const Profile = ({ user }: { user: User }) => {
aria-label="Email"
/>
</div>
{/* <div>
<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>
<Button type="submit">Submit</Button>
</form>
</>
)

View file

@ -1,5 +1,5 @@
import PageSeo from "@components/page-seo"
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);
--gray-alpha: rgba(255, 255, 255, 0.5);
--selection: rgba(255, 255, 255, 0.99);
--border: var(--lighter-gray);
--border: var(--light-gray);
--warning: #ff6700;
--link: #3291ff;
color-scheme: dark;
@ -62,7 +62,7 @@
--gray: #888;
--light-gray: #dedede;
--lighter-gray: #f5f5f5;
--lighter-gray: #f2f2f2;
--lightest-gray: #fafafa;
--darker-gray: #555;
--darkest-gray: #222;
@ -80,7 +80,7 @@
// TODO: replace this with an accessible alternative
*:focus-visible {
outline: none;
outline: none !important;
}
::selection {

View file

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

View file

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

View file

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