More uniform home page spacing, close mobile menu on click

This commit is contained in:
Max Leiter 2022-12-04 14:26:05 -08:00
parent a84dad1dde
commit 72633c6ad2
18 changed files with 164 additions and 230 deletions

View file

@ -1,5 +1,4 @@
.card { .card {
margin: var(--gap) auto;
padding: var(--gap); padding: var(--gap);
border: 1px solid var(--lighter-gray); border: 1px solid var(--lighter-gray);
border-radius: var(--radius); border-radius: var(--radius);

View file

@ -252,7 +252,7 @@ const Post = ({
) )
return ( return (
<div style={{ paddingBottom: 200 }}> <div className={styles.root}>
<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} />

View file

@ -1,3 +1,10 @@
.root {
padding-bottom: 200px;
display: flex;
flex-direction: column;
gap: var(--gap);
}
.buttons { .buttons {
position: relative; position: relative;
display: flex; display: flex;
@ -19,7 +26,6 @@
.description { .description {
width: 100%; width: 100%;
margin-bottom: var(--gap);
} }
@media screen and (max-width: 650px) { @media screen and (max-width: 650px) {

View file

@ -23,7 +23,7 @@ type props = {
const Title = ({ onChange, title }: props) => { const Title = ({ onChange, title }: props) => {
return ( return (
<div className={styles.title}> <div className={styles.title}>
<h1>Drift</h1> <h1 style={{ margin: 0, padding: 0 }}>Drift</h1>
<Input <Input
placeholder={placeholder} placeholder={placeholder}
value={title} value={title}

View file

@ -3,7 +3,7 @@
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: var(--gap); gap: inherit;
} }
@media screen and (max-width: 650px) { @media screen and (max-width: 650px) {

View file

@ -10,8 +10,8 @@ type TitleProps = {
loading?: boolean loading?: boolean
displayName?: string displayName?: string
visibility?: string visibility?: string
createdAt?: Date createdAt?: string
expiresAt?: Date expiresAt?: string
authorId?: string authorId?: string
} }

View file

@ -1,5 +1,4 @@
.card { .card {
margin: var(--gap) auto;
padding: var(--gap); padding: var(--gap);
border: 1px solid var(--light-gray); border: 1px solid var(--light-gray);
border-radius: var(--radius); border-radius: var(--radius);

View file

@ -20,7 +20,7 @@ type Props = {
preview: string preview: string
} }
const DownloadButton = ({ rawLink }: { rawLink?: string }) => { const DownloadButtons = ({ rawLink }: { rawLink?: string }) => {
return ( return (
<div className={styles.actionWrapper}> <div className={styles.actionWrapper}>
<ButtonGroup className={styles.actions}> <ButtonGroup className={styles.actions}>
@ -59,12 +59,6 @@ const Document = ({
skeleton, skeleton,
id id
}: Props) => { }: Props) => {
const rawLink = () => {
if (id) {
return `/file/raw/${id}`
}
}
if (skeleton) { if (skeleton) {
return ( return (
<> <>
@ -98,7 +92,7 @@ const Document = ({
/> />
</Link> </Link>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
<DownloadButton rawLink={rawLink()} /> <DownloadButtons rawLink={`/api/file/raw/${id}`} />
<DocumentTabs <DocumentTabs
defaultTab={initialTab} defaultTab={initialTab}
preview={preview} preview={preview}

View file

@ -57,6 +57,7 @@ const getPost = async (id: string) => {
if (post.visibility === "protected" && !isAuthorOrAdmin) { if (post.visibility === "protected" && !isAuthorOrAdmin) {
return { return {
// TODO: remove this. It's temporary to appease typescript
post: { post: {
visibility: "protected", visibility: "protected",
id: post.id, id: post.id,
@ -64,6 +65,7 @@ const getPost = async (id: string) => {
parentId: "", parentId: "",
title: "", title: "",
createdAt: new Date("1970-01-01"), createdAt: new Date("1970-01-01"),
expiresAt: new Date("1970-01-01"),
author: { author: {
displayName: "", displayName: "",
}, },
@ -108,7 +110,8 @@ const PostView = async ({
/> />
<PostTitle <PostTitle
title={post.title} title={post.title}
createdAt={post.createdAt} createdAt={post.createdAt.toString()}
expiresAt={post.expiresAt?.toString()}
displayName={post.author?.displayName || ""} displayName={post.author?.displayName || ""}
visibility={post.visibility} visibility={post.visibility}
authorId={post.authorId} authorId={post.authorId}

View file

@ -45,7 +45,6 @@
opacity: 1; opacity: 1;
} }
.selectContent { .selectContent {
width: auto; width: auto;
height: 18px; height: 18px;
@ -64,6 +63,10 @@
display: none; display: none;
} }
.contentWrapper {
background: var(--bg);
}
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
.wrapper [data-tab="github"] { .wrapper [data-tab="github"] {
display: none; display: none;
@ -71,6 +74,7 @@
.mobile { .mobile {
margin-top: var(--gap); margin-top: var(--gap);
margin-bottom: var(--gap);
display: flex; display: flex;
} }
@ -79,6 +83,23 @@
flex-direction: column; flex-direction: column;
} }
.dropdownItem:not(:first-child):not(:last-child) :global(button) {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.dropdownItem:first-child :global(button) {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.dropdownItem:last-child :global(button) {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.dropdownItem a, .dropdownItem a,
.dropdownItem button { .dropdownItem button {
width: 100%; width: 100%;

View file

@ -22,7 +22,7 @@ import {
} 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 { useEffect, useMemo, useState } from "react" import { useMemo } from "react"
type Tab = { type Tab = {
name: string name: string
@ -155,17 +155,13 @@ const Header = () => {
] ]
}, [isAdmin, resolvedTheme, isSignedIn, setTheme]) }, [isAdmin, resolvedTheme, isSignedIn, setTheme])
// // 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) const buttons = pages.map(getButton)
// TODO: this is a hack to close the radix ui menu when a next link is clicked
const onClick = () => {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
}
return ( return (
<header className={clsx(styles.header, { <header className={clsx(styles.header, {
[styles.loading]: isLoading, [styles.loading]: isLoading,
@ -188,6 +184,7 @@ const Header = () => {
<DropdownMenu.Item <DropdownMenu.Item
key={button?.key} key={button?.key}
className={styles.dropdownItem} className={styles.dropdownItem}
onClick={onClick}
> >
{button} {button}
</DropdownMenu.Item> </DropdownMenu.Item>

View file

@ -2,15 +2,6 @@ import clsx from "clsx"
import React from "react" import React from "react"
import styles from "./input.module.css" import styles from "./input.module.css"
type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<
T,
Exclude<keyof T, Keys>
> &
{
[K in Keys]-?: Required<Pick<T, K>> &
Partial<Record<Exclude<Keys, K>, undefined>>
}[Keys]
type Props = React.HTMLProps<HTMLInputElement> & { type Props = React.HTMLProps<HTMLInputElement> & {
label?: string label?: string
width?: number | string width?: number | string
@ -18,8 +9,30 @@ type Props = React.HTMLProps<HTMLInputElement> & {
labelClassName?: string labelClassName?: string
} }
type InputProps = RequireOnlyOne<Props, "label" | "aria-label"> // we have two special rules on top of the props:
// if onChange or value is passed, we require both
// if label is passed, we forbid aria-label and vice versa
type InputProps = Omit<Props, "onChange" | "value" | "label" | "aria-label"> &
(
| {
onChange: Props["onChange"]
value: Props["value"]
} // if onChange or value is passed, we require both
| {
onChange?: never
value?: never
}
) &
(
| {
label: Props["label"]
"aria-label"?: never
} // if label is passed, we forbid aria-label and vice versa
| {
label?: never
"aria-label": Props["aria-label"]
}
)
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
const Input = React.forwardRef<HTMLInputElement, InputProps>( const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ label, className, width, height, labelClassName, ...props }, ref) => { ({ label, className, width, height, labelClassName, ...props }, ref) => {

View file

@ -51,12 +51,6 @@
white-space: nowrap; white-space: nowrap;
} }
@media screen and (max-width: 768px) {
.wrapper {
margin-bottom: var(--gap);
}
}
.input:disabled { .input:disabled {
background-color: var(--lighter-gray); background-color: var(--lighter-gray);
color: var(--fg); color: var(--fg);

View file

@ -2,14 +2,20 @@
import styles from "./post-list.module.css" import styles from "./post-list.module.css"
import ListItem from "./list-item" import ListItem from "./list-item"
import { ChangeEvent, useCallback, useEffect, useState } from "react" import {
import useDebounce from "@lib/hooks/use-debounce" ChangeEvent,
useCallback,
useDeferredValue,
useEffect,
useState
} from "react"
import Link from "@components/link" import Link from "@components/link"
import type { PostWithFiles } from "@lib/server/prisma" import type { PostWithFiles } from "@lib/server/prisma"
import Input from "@components/input" import Input from "@components/input"
import Button from "@components/button" import Button from "@components/button"
import { useToasts } from "@components/toasts" import { useToasts } from "@components/toasts"
import { ListItemSkeleton } from "./list-item-skeleton" import { ListItemSkeleton } from "./list-item-skeleton"
import debounce from "lodash.debounce"
type Props = { type Props = {
initialPosts: string | PostWithFiles[] initialPosts: string | PostWithFiles[]
@ -32,8 +38,6 @@ const PostList = ({
const [hasMorePosts, setHasMorePosts] = useState(morePosts) const [hasMorePosts, setHasMorePosts] = useState(morePosts)
const { setToast } = useToasts() const { setToast } = useToasts()
const debouncedSearchValue = useDebounce(search, 200)
const loadMoreClick = useCallback( const loadMoreClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => { (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault() e.preventDefault()
@ -56,36 +60,30 @@ const PostList = ({
[posts, hasMorePosts] [posts, hasMorePosts]
) )
// update posts on search const onSearch = (query: string) => {
useEffect(() => { setSearching(true)
if (debouncedSearchValue) { async function fetchPosts() {
setSearching(true) const res = await fetch(
async function fetchPosts() { `/api/post/search?q=${encodeURIComponent(query)}&userId=${userId}`,
const res = await fetch( {
`/api/post/search?q=${encodeURIComponent( method: "GET",
debouncedSearchValue headers: {
)}&userId=${userId}`, "Content-Type": "application/json"
{
method: "GET",
headers: {
"Content-Type": "application/json"
}
} }
) }
const json = await res.json() )
setPosts(json) const json = await res.json()
setSearching(false) setPosts(json)
} setSearching(false)
fetchPosts()
} else {
setPosts(initialPosts)
} }
// TODO: fix cyclical dependency issue fetchPosts()
// eslint-disable-next-line react-hooks/exhaustive-deps }
}, [debouncedSearchValue, userId])
const debouncedSearch = debounce(onSearch, 500)
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => { const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value) setSearchValue(e.target.value)
debouncedSearch(e.target.value)
} }
const deletePost = useCallback( const deletePost = useCallback(
@ -117,10 +115,11 @@ const PostList = ({
disabled={!posts} disabled={!posts}
style={{ maxWidth: 300 }} style={{ maxWidth: 300 }}
aria-label="Search" aria-label="Search"
value={search}
/> />
</div> </div>
{!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>} {!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>}
{!posts?.length && ( {searching && (
<ul> <ul>
<ListItemSkeleton /> <ListItemSkeleton />
<ListItemSkeleton /> <ListItemSkeleton />

View file

@ -15,7 +15,7 @@
"dependencies": { "dependencies": {
"@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.7.1",
"@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.1", "@radix-ui/react-dropdown-menu": "^2.0.1",
"@radix-ui/react-popover": "^1.0.2", "@radix-ui/react-popover": "^1.0.2",
@ -26,6 +26,7 @@
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"client-zip": "2.2.1", "client-zip": "2.2.1",
"jest": "^29.3.1", "jest": "^29.3.1",
"lodash.debounce": "^4.0.8",
"next": "13.0.7-canary.1", "next": "13.0.7-canary.1",
"next-auth": "^4.18.0", "next-auth": "^4.18.0",
"prisma": "^4.7.1", "prisma": "^4.7.1",
@ -41,6 +42,7 @@
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.1.6", "@next/bundle-analyzer": "12.1.6",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "17.0.23", "@types/node": "17.0.23",
"@types/react": "18.0.9", "@types/react": "18.0.9",
"@types/react-datepicker": "4.4.1", "@types/react-datepicker": "4.4.1",

View file

@ -4,13 +4,14 @@ specifiers:
'@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
'@prisma/client': ^4.6.1 '@prisma/client': ^4.7.1
'@radix-ui/react-dialog': ^1.0.2 '@radix-ui/react-dialog': ^1.0.2
'@radix-ui/react-dropdown-menu': ^2.0.1 '@radix-ui/react-dropdown-menu': ^2.0.1
'@radix-ui/react-popover': ^1.0.2 '@radix-ui/react-popover': ^1.0.2
'@radix-ui/react-tabs': ^1.0.1 '@radix-ui/react-tabs': ^1.0.1
'@radix-ui/react-tooltip': ^1.0.2 '@radix-ui/react-tooltip': ^1.0.2
'@types/bcrypt': ^5.0.0 '@types/bcrypt': ^5.0.0
'@types/lodash.debounce': ^4.0.7
'@types/node': 17.0.23 '@types/node': 17.0.23
'@types/react': 18.0.9 '@types/react': 18.0.9
'@types/react-datepicker': 4.4.1 '@types/react-datepicker': 4.4.1
@ -25,6 +26,7 @@ specifiers:
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
lodash.debounce: ^4.0.8
next: 13.0.7-canary.1 next: 13.0.7-canary.1
next-auth: ^4.18.0 next-auth: ^4.18.0
next-unused: 0.0.6 next-unused: 0.0.6
@ -43,9 +45,9 @@ specifiers:
typescript-plugin-css-modules: 3.4.0 typescript-plugin-css-modules: 3.4.0
dependencies: dependencies:
'@next-auth/prisma-adapter': 1.0.5_qwexivae5olc6wqfcmxswm7qjy '@next-auth/prisma-adapter': 1.0.5_hpttyne5hky44pj2anoxcmv4zm
'@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.7.1 '@prisma/client': 4.7.1_prisma@4.7.1
'@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u '@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
'@radix-ui/react-dropdown-menu': 2.0.1_jbvntnid6ohjelon6ccj5dhg2u '@radix-ui/react-dropdown-menu': 2.0.1_jbvntnid6ohjelon6ccj5dhg2u
'@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u '@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
@ -56,6 +58,7 @@ dependencies:
bcrypt: 5.1.0 bcrypt: 5.1.0
client-zip: 2.2.1 client-zip: 2.2.1
jest: 29.3.1_@types+node@17.0.23 jest: 29.3.1_@types+node@17.0.23
lodash.debounce: 4.0.8
next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y
next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq
prisma: 4.7.1 prisma: 4.7.1
@ -74,6 +77,7 @@ optionalDependencies:
devDependencies: devDependencies:
'@next/bundle-analyzer': 12.1.6 '@next/bundle-analyzer': 12.1.6
'@types/bcrypt': 5.0.0 '@types/bcrypt': 5.0.0
'@types/lodash.debounce': 4.0.7
'@types/node': 17.0.23 '@types/node': 17.0.23
'@types/react': 18.0.9 '@types/react': 18.0.9
'@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y '@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y
@ -785,13 +789,13 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@next-auth/prisma-adapter/1.0.5_qwexivae5olc6wqfcmxswm7qjy: /@next-auth/prisma-adapter/1.0.5_hpttyne5hky44pj2anoxcmv4zm:
resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==} resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==}
peerDependencies: peerDependencies:
'@prisma/client': '>=2.26.0 || >=3' '@prisma/client': '>=2.26.0 || >=3'
next-auth: ^4 next-auth: ^4
dependencies: dependencies:
'@prisma/client': 4.6.1_prisma@4.7.1 '@prisma/client': 4.7.1_prisma@4.7.1
next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq
dev: false dev: false
@ -969,8 +973,8 @@ packages:
/@popperjs/core/2.11.6: /@popperjs/core/2.11.6:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
/@prisma/client/4.6.1_prisma@4.7.1: /@prisma/client/4.7.1_prisma@4.7.1:
resolution: {integrity: sha512-M1+NNrMzqaOIxT7PBGcTs3IZo7d1EW/+gVQd4C4gUgWBDGgD9AcIeZnUSidgWClmpMSgVUdnVORjsWWGUameYA==} resolution: {integrity: sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
requiresBuild: true requiresBuild: true
peerDependencies: peerDependencies:
@ -979,12 +983,12 @@ packages:
prisma: prisma:
optional: true optional: true
dependencies: dependencies:
'@prisma/engines-version': 4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32 '@prisma/engines-version': 4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c
prisma: 4.7.1 prisma: 4.7.1
dev: false dev: false
/@prisma/engines-version/4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32: /@prisma/engines-version/4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c:
resolution: {integrity: sha512-HUCmkXAU2jqp2O1RvNtbE+seLGLyJGEABZS/R38rZjSAafAy0WzBuHq+tbZMnD+b5OSCsTVtIPVcuvx1ySxcWQ==} resolution: {integrity: sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==}
dev: false dev: false
/@prisma/engines/4.7.1: /@prisma/engines/4.7.1:
@ -1519,6 +1523,16 @@ packages:
resolution: {integrity: sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==} resolution: {integrity: sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==}
dev: false dev: false
/@types/lodash.debounce/4.0.7:
resolution: {integrity: sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==}
dependencies:
'@types/lodash': 4.14.191
dev: true
/@types/lodash/4.14.191:
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
dev: true
/@types/mdast/3.0.10: /@types/mdast/3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
dependencies: dependencies:
@ -4584,6 +4598,10 @@ packages:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
dev: true dev: true
/lodash.debounce/4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: false
/lodash.memoize/4.1.2: /lodash.memoize/4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
dev: false dev: false

View file

@ -1,109 +0,0 @@
-- CreateTable
CREATE TABLE "SequelizeMeta" (
"name" TEXT NOT NULL,
CONSTRAINT "SequelizeMeta_pkey" PRIMARY KEY ("name")
);
-- CreateTable
CREATE TABLE "files" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"sha" TEXT NOT NULL,
"html" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"userId" TEXT NOT NULL,
"postId" TEXT NOT NULL,
CONSTRAINT "files_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "posts" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"visibility" TEXT NOT NULL,
"password" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"expiresAt" TIMESTAMP(3),
"parentId" TEXT,
"description" TEXT,
"authorId" TEXT NOT NULL,
CONSTRAINT "posts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "accounts" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"refresh_token_expires_in" INTEGER,
CONSTRAINT "accounts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"name" TEXT,
"email" TEXT,
"emailVerified" TIMESTAMP(3),
"image" TEXT,
"username" TEXT,
"role" TEXT DEFAULT 'user',
"password" TEXT,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "verification_tokens" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "accounts_provider_providerAccountId_key" ON "accounts"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
-- CreateIndex
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
-- CreateIndex
CREATE UNIQUE INDEX "verification_tokens_token_key" ON "verification_tokens"("token");
-- CreateIndex
CREATE UNIQUE INDEX "verification_tokens_identifier_token_key" ON "verification_tokens"("identifier", "token");

View file

@ -1,15 +1,14 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity", "fullTextSearch"] previewFeatures = ["fullTextSearch"]
} }
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
referentialIntegrity = "prisma" relationMode = "prisma"
} }
model SequelizeMeta { model SequelizeMeta {
name String @id name String @id
} }
@ -18,7 +17,7 @@ model File {
id String @id @default(cuid()) id String @id @default(cuid())
title String title String
content String content String
sha String sha String
html String html String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@ -27,6 +26,7 @@ model File {
postId String postId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade) post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
@@index([postId, userId, id])
@@map("files") @@map("files")
} }
@ -41,37 +41,35 @@ model Post {
expiresAt DateTime? expiresAt DateTime?
parentId String? parentId String?
description String? description String?
author User? @relation(fields: [authorId], references: [id])
authorId String authorId String
files File[] files File[]
author User? @relation(fields: [authorId], references: [id])
@@index([authorId, id])
@@map("posts") @@map("posts")
} }
// Next auth stuff, from https://next-auth.js.org/adapters/prisma
model Account { model Account {
id String @id @default(cuid()) id String @id @default(cuid())
userId String userId String
type String type String
provider String provider String
providerAccountId String providerAccountId String
refresh_token String? @db.Text refresh_token String?
access_token String? @db.Text access_token String?
expires_at Int? expires_at Int?
token_type String? token_type String?
scope String? scope String?
id_token String? @db.Text id_token String?
session_state String? session_state String?
createdAt DateTime @default(now()) @map(name: "created_at") createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at") updatedAt DateTime @default(now()) @map("updated_at")
// https://next-auth.js.org/providers/github
refresh_token_expires_in Int? refresh_token_expires_in Int?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId]) @@unique([provider, providerAccountId])
@@map(name: "accounts") @@index([userId, providerAccountId], map: "accounts_provider_account_id")
@@map("accounts")
} }
model Session { model Session {
@ -80,6 +78,9 @@ model Session {
userId String userId String
expires DateTime expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, expires], map: "sessions_user_id_expires")
@@map("sessions")
} }
model User { model User {
@ -88,16 +89,13 @@ model User {
email String? @unique email String? @unique
emailVerified DateTime? emailVerified DateTime?
image String? image String?
role String? @default("user")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
displayName String?
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
posts Post[]
accounts Account[] accounts Account[]
sessions Session[] sessions Session[]
// custom fields
posts Post[]
role String? @default("user")
displayName String?
@@map("users") @@map("users")
} }