remove more of geist-ui: add spinner, button dropdown, toasts. bump deps
This commit is contained in:
parent
0cab3acd62
commit
d6894ffb8b
35 changed files with 397 additions and 459 deletions
|
@ -60,7 +60,7 @@
|
|||
}
|
||||
|
||||
.chevron {
|
||||
transition: transform 0.2s ease-in-out;
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
[data-state="open"] .chevron {
|
||||
|
|
|
@ -2,7 +2,8 @@ import { memo, useEffect, useState } from "react"
|
|||
import styles from "./preview.module.css"
|
||||
import "@styles/markdown.css"
|
||||
import "@styles/syntax.css"
|
||||
import { Spinner } from "@geist-ui/core/dist"
|
||||
import Skeleton from "@components/skeleton"
|
||||
import { Spinner } from "@components/spinner"
|
||||
|
||||
type Props = {
|
||||
height?: number | string
|
||||
|
@ -52,9 +53,7 @@ const MarkdownPreview = ({
|
|||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
</>
|
||||
<Spinner />
|
||||
) : (
|
||||
<StaticPreview preview={preview} height={height} />
|
||||
)}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use client"
|
||||
|
||||
import * as RadixTabs from "@radix-ui/react-tabs"
|
||||
import FormattingIcons from "app/(posts)/new/components/edit-document-list/edit-document/formatting-icons"
|
||||
import { ChangeEvent, useRef } from "react"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
border-radius: 2px;
|
||||
border: 2px dashed var(--border) !important;
|
||||
outline: none;
|
||||
transition: all 0.24s ease-in-out;
|
||||
transition: all 0.14s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
|||
.error {
|
||||
color: red;
|
||||
font-size: 0.8rem;
|
||||
transition: border 0.24s ease-in-out;
|
||||
transition: border 0.14s ease-in-out;
|
||||
border: 2px solid red;
|
||||
border-radius: 2px;
|
||||
padding: 20px;
|
||||
|
@ -42,3 +42,13 @@
|
|||
margin: 0;
|
||||
padding-left: var(--gap-double);
|
||||
}
|
||||
|
||||
.verb:after {
|
||||
content: "click";
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.verb:after {
|
||||
content: "tap";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { useMediaQuery, useTheme, useToasts } from "@geist-ui/core/dist"
|
||||
import { useDropzone } from "react-dropzone"
|
||||
import styles from "./drag-and-drop.module.css"
|
||||
import generateUUID from "@lib/generate-uuid"
|
||||
|
@ -9,13 +8,10 @@ import {
|
|||
} from "@lib/constants"
|
||||
import byteToMB from "@lib/byte-to-mb"
|
||||
import type { Document } from "../new"
|
||||
import { useToasts } from "@components/toasts"
|
||||
|
||||
function FileDropzone({ setDocs }: { setDocs: (docs: Document[]) => void }) {
|
||||
const { palette } = useTheme()
|
||||
const { setToast } = useToasts()
|
||||
const isMobile = useMediaQuery("xs", {
|
||||
match: "down"
|
||||
})
|
||||
const onDrop = async (acceptedFiles: File[]) => {
|
||||
const newDocs = await Promise.all(
|
||||
acceptedFiles.map((file) => {
|
||||
|
@ -23,9 +19,9 @@ function FileDropzone({ setDocs }: { setDocs: (docs: Document[]) => void }) {
|
|||
const reader = new FileReader()
|
||||
|
||||
reader.onabort = () =>
|
||||
setToast({ text: "File reading was aborted", type: "error" })
|
||||
setToast({ message: "File reading was aborted", type: "error" })
|
||||
reader.onerror = () =>
|
||||
setToast({ text: "File reading failed", type: "error" })
|
||||
setToast({ message: "File reading failed", type: "error" })
|
||||
reader.onload = () => {
|
||||
const content = reader.result as string
|
||||
resolve({
|
||||
|
@ -84,20 +80,13 @@ function FileDropzone({ setDocs }: { setDocs: (docs: Document[]) => void }) {
|
|||
</li>
|
||||
))
|
||||
|
||||
const verb = isMobile ? "tap" : "click"
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={styles.dropzone}
|
||||
style={{
|
||||
borderColor: palette.accents_3
|
||||
}}
|
||||
>
|
||||
<div {...getRootProps()} className={styles.dropzone}>
|
||||
<input {...getInputProps()} />
|
||||
{!isDragActive && (
|
||||
<p style={{ color: "var(--gray)" }}>
|
||||
Drag some files here, or {verb} to select files
|
||||
Drag some files here, or <span className={styles.verb} /> to select files
|
||||
</p>
|
||||
)}
|
||||
{isDragActive && <p>Release to drop the files here</p>}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.card {
|
||||
margin: var(--gap) auto;
|
||||
padding: var(--gap);
|
||||
border: 1px solid var(--light-gray);
|
||||
border: 1px solid var(--lighter-gray);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
.actionWrapper .actions {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -34px;
|
||||
top: -40px;
|
||||
}
|
||||
|
||||
/* small screens, top: 0 */
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"use client"
|
||||
|
||||
import { useToasts, ButtonDropdown } from "@geist-ui/core/dist"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useCallback, useState } from "react"
|
||||
import generateUUID from "@lib/generate-uuid"
|
||||
|
@ -16,6 +15,8 @@ import Title from "./title"
|
|||
import FileDropzone from "./drag-and-drop"
|
||||
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: "",
|
||||
|
@ -87,7 +88,8 @@ const Post = ({
|
|||
const json = await res.json()
|
||||
console.error(json)
|
||||
setToast({
|
||||
text: "Please fill out all fields",
|
||||
id: "error",
|
||||
message: "Please fill out all fields",
|
||||
type: "error"
|
||||
})
|
||||
setPasswordModalVisible(false)
|
||||
|
@ -114,7 +116,7 @@ const Post = ({
|
|||
|
||||
if (!title) {
|
||||
setToast({
|
||||
text: "Please fill out the post title",
|
||||
message: "Please fill out the post title",
|
||||
type: "error"
|
||||
})
|
||||
hasErrored = true
|
||||
|
@ -122,7 +124,7 @@ const Post = ({
|
|||
|
||||
if (!docs.length) {
|
||||
setToast({
|
||||
text: "Please add at least one document",
|
||||
message: "Please add at least one document",
|
||||
type: "error"
|
||||
})
|
||||
hasErrored = true
|
||||
|
@ -131,7 +133,7 @@ const Post = ({
|
|||
for (const doc of docs) {
|
||||
if (!doc.title) {
|
||||
setToast({
|
||||
text: "Please fill out all the document titles",
|
||||
message: "Please fill out all the document titles",
|
||||
type: "error"
|
||||
})
|
||||
hasErrored = true
|
||||
|
@ -308,19 +310,27 @@ const Post = ({
|
|||
enableTabLoop={false}
|
||||
minDate={new Date()}
|
||||
/>
|
||||
<ButtonDropdown loading={isSubmitting} type="success">
|
||||
<ButtonDropdown.Item main onClick={() => onSubmit("unlisted")}>
|
||||
<ButtonDropdown iconHeight={40}>
|
||||
<Button
|
||||
height={40}
|
||||
width={251}
|
||||
onClick={() => onSubmit("unlisted")}
|
||||
>
|
||||
Create Unlisted
|
||||
</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit("private")}>
|
||||
</Button>
|
||||
<Button height={40} width={300} onClick={() => onSubmit("private")}>
|
||||
Create Private
|
||||
</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit("public")}>
|
||||
</Button>
|
||||
<Button height={40} width={300} onClick={() => onSubmit("public")}>
|
||||
Create Public
|
||||
</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit("protected")}>
|
||||
</Button>
|
||||
<Button
|
||||
height={40}
|
||||
width={300}
|
||||
onClick={() => onSubmit("protected")}
|
||||
>
|
||||
Create with Password
|
||||
</ButtonDropdown.Item>
|
||||
</Button>
|
||||
</ButtonDropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,14 +13,14 @@ const titlePlaceholders = [
|
|||
"I'm thinking about ..."
|
||||
]
|
||||
|
||||
const placeholder = titlePlaceholders[3]
|
||||
|
||||
type props = {
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
title?: string
|
||||
}
|
||||
|
||||
const Title = ({ onChange, title }: props) => {
|
||||
const placeholder =
|
||||
titlePlaceholders[Math.floor(Math.random() * titlePlaceholders.length)]
|
||||
return (
|
||||
<div className={styles.title}>
|
||||
<h1>Drift</h1>
|
||||
|
|
|
@ -4,7 +4,6 @@ import VisibilityBadge from "@components/badges/visibility-badge"
|
|||
import DocumentComponent from "./view-document"
|
||||
import styles from "./post-page.module.css"
|
||||
|
||||
import { useMediaQuery } from "@geist-ui/core/dist"
|
||||
import { useEffect, useState } from "react"
|
||||
import Archive from "@geist-ui/icons/archive"
|
||||
import Edit from "@geist-ui/icons/edit"
|
||||
|
@ -32,7 +31,6 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
|
|||
)
|
||||
const [visibility, setVisibility] = useState<string>(post.visibility)
|
||||
const router = useRouter()
|
||||
const isMobile = useMediaQuery("mobile")
|
||||
|
||||
useEffect(() => {
|
||||
if (post.expiresAt) {
|
||||
|
@ -101,7 +99,7 @@ const PostPage = ({ post: initialPost, isProtected, isAuthor }: Props) => {
|
|||
{!isAvailable && <PasswordModalPage setPost={setPost} postId={post.id} />}
|
||||
<div className={styles.header}>
|
||||
<span className={styles.buttons}>
|
||||
<ButtonGroup vertical={isMobile}>
|
||||
<ButtonGroup verticalIfMobile>
|
||||
<Button
|
||||
iconLeft={<Edit />}
|
||||
onClick={editACopy}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useToasts } from "@geist-ui/core/dist"
|
||||
import { Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||
import PasswordModal from "@components/password-modal"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
import { useToasts } from "@components/toasts"
|
||||
|
||||
type Props = {
|
||||
setPost: (post: PostWithFilesAndAuthor) => void
|
||||
|
@ -25,7 +25,7 @@ const PasswordModalPage = ({ setPost, postId }: Props) => {
|
|||
if (!res.ok) {
|
||||
setToast({
|
||||
type: "error",
|
||||
text: "Wrong password"
|
||||
message: "Wrong password"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ const PasswordModalPage = ({ setPost, postId }: Props) => {
|
|||
if (data) {
|
||||
if (data.error) {
|
||||
setToast({
|
||||
text: data.error,
|
||||
message: data.error,
|
||||
type: "error"
|
||||
})
|
||||
} else {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
@media screen and (max-width: 768px) {
|
||||
.header .title {
|
||||
flex-direction: column;
|
||||
gap: var(--gap-half);
|
||||
|
@ -50,4 +50,8 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.controls {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
import SettingsGroup from "@components/settings-group"
|
||||
import { Fieldset, useToasts } from "@geist-ui/core/dist"
|
||||
import { Fieldset } from "@geist-ui/core/dist"
|
||||
import byteToMB from "@lib/byte-to-mb"
|
||||
import { PostWithFiles } from "@lib/server/prisma"
|
||||
import Table from "rc-table"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
"use client"
|
||||
import { Fieldset, useToasts } from "@geist-ui/core/dist"
|
||||
import { Fieldset } from "@geist-ui/core/dist"
|
||||
import Table from "rc-table"
|
||||
import ActionDropdown from "./action-dropdown"
|
||||
import SettingsGroup from "@components/settings-group"
|
||||
import type { User, UserWithPosts } from "@lib/server/prisma"
|
||||
import { useState } from "react"
|
||||
import { useToasts } from "@components/toasts"
|
||||
|
||||
const UserTable = ({ users: initial }: { users: UserWithPosts[] }) => {
|
||||
const [users, setUsers] = useState(initial)
|
||||
|
@ -21,7 +22,7 @@ const UserTable = ({ users: initial }: { users: UserWithPosts[] }) => {
|
|||
|
||||
if (res.status === 200) {
|
||||
setToast({
|
||||
text: "Role updated",
|
||||
message: "Role updated",
|
||||
type: "success"
|
||||
})
|
||||
|
||||
|
@ -39,7 +40,7 @@ const UserTable = ({ users: initial }: { users: UserWithPosts[] }) => {
|
|||
})
|
||||
} else {
|
||||
setToast({
|
||||
text: "Something went wrong",
|
||||
message: "Something went wrong",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Loading, useToasts } from "@geist-ui/core/dist"
|
||||
import PasswordModal from "@components/password-modal"
|
||||
import { useCallback, useState } from "react"
|
||||
import ButtonGroup from "@components/button-group"
|
||||
import Button from "@components/button"
|
||||
import { useToasts } from "@components/toasts"
|
||||
import { Spinner } from "@components/spinner"
|
||||
|
||||
type Props = {
|
||||
postId: string
|
||||
|
@ -30,7 +31,7 @@ const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
|
|||
setVisibility(json.visibility)
|
||||
} else {
|
||||
setToast({
|
||||
text: "An error occurred",
|
||||
message: "An error occurred",
|
||||
type: "error"
|
||||
})
|
||||
setPasswordModalVisible(false)
|
||||
|
@ -65,9 +66,9 @@ const VisibilityControl = ({ postId, visibility, setVisibility }: Props) => {
|
|||
return (
|
||||
<>
|
||||
{isSubmitting ? (
|
||||
<Loading />
|
||||
<Spinner />
|
||||
) : (
|
||||
<ButtonGroup>
|
||||
<ButtonGroup verticalIfMobile>
|
||||
<Button
|
||||
disabled={visibility === "private"}
|
||||
onClick={() => onSubmit("private")}
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
.main {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.dropdownContent {
|
||||
background-clip: padding-box;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
|
||||
.dropdown > button:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.dropdown > button:last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
/* if we draw bother borders it will be 2 thick */
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.dropdown > button:last-child:hover {
|
||||
/* because we removed the left border we need to mock it */
|
||||
outline: 1px solid var(--darker-gray);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import React, { useCallback, useEffect } from "react"
|
|||
import { useState } from "react"
|
||||
import styles from "./dropdown.module.css"
|
||||
import DownIcon from "@geist-ui/icons/arrowDown"
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
|
||||
type Props = {
|
||||
type?: "primary" | "secondary"
|
||||
loading?: boolean
|
||||
|
@ -17,99 +18,37 @@ type ButtonDropdownProps = Props & Attrs
|
|||
const ButtonDropdown: React.FC<
|
||||
React.PropsWithChildren<ButtonDropdownProps>
|
||||
> = ({ type, className, disabled, loading, iconHeight = 24, ...props }) => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [dropdown, setDropdown] = useState<HTMLDivElement | null>(null)
|
||||
|
||||
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
setVisible(!visible)
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
setVisible(false)
|
||||
}
|
||||
|
||||
const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
const onMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
const onMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
setVisible(false)
|
||||
}
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === "Escape") {
|
||||
setVisible(false)
|
||||
}
|
||||
}
|
||||
|
||||
const onClickOutside = useCallback(
|
||||
() => (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (dropdown && !dropdown.contains(e.target as Node)) {
|
||||
setVisible(false)
|
||||
}
|
||||
},
|
||||
[dropdown]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
document.addEventListener("mousedown", onClickOutside)
|
||||
} else {
|
||||
document.removeEventListener("mousedown", onClickOutside)
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", onClickOutside)
|
||||
}
|
||||
}, [visible, onClickOutside])
|
||||
|
||||
if (!Array.isArray(props.children)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.main} ${className || ""}`}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end"
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Root>
|
||||
<div className={styles.dropdown}>
|
||||
{props.children[0]}
|
||||
<Button
|
||||
style={{ height: iconHeight, width: iconHeight }}
|
||||
className={styles.icon}
|
||||
onClick={() => setVisible(!visible)}
|
||||
<DropdownMenu.Trigger
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end"
|
||||
}}
|
||||
asChild
|
||||
>
|
||||
<DownIcon />
|
||||
</Button>
|
||||
<Button
|
||||
iconLeft={<DownIcon />}
|
||||
type={type}
|
||||
className={styles.icon}
|
||||
/>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content align="end">
|
||||
{props.children.slice(1).map((child, index) => (
|
||||
<DropdownMenu.Item key={index}>{child}</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</div>
|
||||
{visible && (
|
||||
<div className={`${styles.dropdown}`}>
|
||||
<div className={`${styles.dropdownContent}`}>
|
||||
{props.children.slice(1)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,45 @@
|
|||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.button-group > * {
|
||||
flex: 1 1 auto;
|
||||
margin: 0;
|
||||
flex: 1 1 auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button-group > * {
|
||||
border-radius: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.button-group > button:first-of-type {
|
||||
border-top-left-radius: var(--radius) !important;
|
||||
border-bottom-left-radius: var(--radius) !important;
|
||||
border-top-left-radius: var(--radius) !important;
|
||||
border-bottom-left-radius: var(--radius) !important;
|
||||
}
|
||||
|
||||
.button-group > button:last-of-type {
|
||||
border-top-right-radius: var(--radius) !important;
|
||||
border-bottom-right-radius: var(--radius) !important;
|
||||
border-top-right-radius: var(--radius) !important;
|
||||
border-bottom-right-radius: var(--radius) !important;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
@media screen and (max-width: 768px) {
|
||||
.verticalIfMobile {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.verticalIfMobile.button-group > button:first-of-type {
|
||||
border-top-left-radius: var(--radius) !important;
|
||||
border-top-right-radius: var(--radius) !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.verticalIfMobile.button-group > button:last-of-type {
|
||||
border-top-left-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-left-radius: var(--radius) !important;
|
||||
border-bottom-right-radius: var(--radius) !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,18 @@ import styles from "./button-group.module.css"
|
|||
import clsx from "clsx"
|
||||
export default function ButtonGroup({
|
||||
children,
|
||||
vertical,
|
||||
verticalIfMobile,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode | React.ReactNode[]
|
||||
vertical?: boolean
|
||||
verticalIfMobile?: boolean
|
||||
} & React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
props.className,
|
||||
styles["button-group"],
|
||||
vertical && styles.vertical
|
||||
verticalIfMobile && styles.verticalIfMobile
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import React, { useEffect, useState } from "react"
|
||||
import MoonIcon from "@geist-ui/icons/moon"
|
||||
import SunIcon from "@geist-ui/icons/sun"
|
||||
import styles from "./header.module.css"
|
||||
import { Select } from "@geist-ui/core/dist"
|
||||
import { useTheme } from "@components/theme/ThemeClientContextProvider"
|
||||
|
||||
const Controls = () => {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const switchThemes = () => {
|
||||
if (theme === "dark") {
|
||||
setTheme("light")
|
||||
} else {
|
||||
setTheme("dark")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Select scale={0.5} h="28px" pure onChange={switchThemes} value={theme}>
|
||||
<Select.Option value="light">
|
||||
<span className={styles.selectContent}>
|
||||
<SunIcon size={14} /> Light
|
||||
</span>
|
||||
</Select.Option>
|
||||
<Select.Option value="dark">
|
||||
<span className={styles.selectContent}>
|
||||
<MoonIcon size={14} /> Dark
|
||||
</span>
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Controls)
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { Page, useBodyScroll, useMediaQuery } from "@geist-ui/core/dist"
|
||||
import { useBodyScroll, useMediaQuery } from "@geist-ui/core/dist"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import styles from "./header.module.css"
|
||||
|
@ -174,7 +174,7 @@ const Header = ({ signedIn = false, isAdmin = false }) => {
|
|||
const buttons = pages.map(getButton)
|
||||
|
||||
return (
|
||||
<Page.Header>
|
||||
<header>
|
||||
<div className={styles.tabs}>
|
||||
<div className={styles.buttons}>{buttons}</div>
|
||||
</div>
|
||||
|
@ -189,7 +189,7 @@ const Header = ({ signedIn = false, isAdmin = false }) => {
|
|||
{buttons}
|
||||
</div>
|
||||
)}
|
||||
</Page.Header>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
"use client"
|
||||
import { Tabs, Textarea } from "@geist-ui/core/dist"
|
||||
import Image from "next/image"
|
||||
import styles from "./home.module.css"
|
||||
// TODO:components/new-post/ move these styles
|
||||
import markdownStyles from "app/(posts)/components/preview/preview.module.css"
|
||||
import Card from "./card"
|
||||
import DocumentTabs from "app/(posts)/components/tabs"
|
||||
const Home = ({
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
}
|
||||
|
||||
.warning {
|
||||
background: #f33;
|
||||
background: var(--warning);
|
||||
color: var(--bg);
|
||||
}
|
||||
|
||||
.error {
|
||||
|
@ -21,7 +22,7 @@
|
|||
|
||||
[data-theme="light"] .warning,
|
||||
[data-theme="light"] .error {
|
||||
color: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.type {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"use client"
|
||||
|
||||
import styles from "./post-list.module.css"
|
||||
import ListItemSkeleton from "./list-item-skeleton"
|
||||
import { ListItemSkeleton } from "./list-item-skeleton"
|
||||
import ListItem from "./list-item"
|
||||
import { ChangeEvent, useCallback, useEffect, useState } from "react"
|
||||
import useDebounce from "@lib/hooks/use-debounce"
|
||||
|
@ -9,6 +9,7 @@ import Link from "@components/link"
|
|||
import type { PostWithFiles } from "@lib/server/prisma"
|
||||
import Input from "@components/input"
|
||||
import Button from "@components/button"
|
||||
import { useToasts } from "@components/toasts"
|
||||
|
||||
type Props = {
|
||||
initialPosts: string | PostWithFiles[]
|
||||
|
@ -29,6 +30,7 @@ const PostList = ({
|
|||
const [posts, setPosts] = useState<PostWithFiles[]>(initialPosts)
|
||||
const [searching, setSearching] = useState(false)
|
||||
const [hasMorePosts, setHasMorePosts] = useState(morePosts)
|
||||
const { setToast } = useToasts()
|
||||
|
||||
const debouncedSearchValue = useDebounce(search, 200)
|
||||
|
||||
|
@ -71,7 +73,7 @@ const PostList = ({
|
|||
}
|
||||
)
|
||||
const json = await res.json()
|
||||
setPosts(json.posts)
|
||||
setPosts(json)
|
||||
setSearching(false)
|
||||
}
|
||||
fetchPosts()
|
||||
|
@ -97,9 +99,13 @@ const PostList = ({
|
|||
return
|
||||
} else {
|
||||
setPosts((posts) => posts.filter((post) => post.id !== postId))
|
||||
setToast({
|
||||
message: "Post deleted",
|
||||
type: "success"
|
||||
})
|
||||
}
|
||||
},
|
||||
[]
|
||||
[setToast]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -108,21 +114,18 @@ const PostList = ({
|
|||
<Input
|
||||
placeholder="Search..."
|
||||
onChange={handleSearchChange}
|
||||
disabled={Boolean(!posts?.length)}
|
||||
disabled={!posts}
|
||||
style={{ maxWidth: 300 }}
|
||||
aria-label="Search"
|
||||
/>
|
||||
</div>
|
||||
{!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>}
|
||||
{!posts?.length && searching && (
|
||||
{/* {!posts?.length && (
|
||||
<ul>
|
||||
<li>
|
||||
<ListItemSkeleton />
|
||||
</li>
|
||||
<li>
|
||||
<ListItemSkeleton />
|
||||
</li>
|
||||
<ListItemSkeleton />
|
||||
<ListItemSkeleton />
|
||||
</ul>
|
||||
)}
|
||||
)} */}
|
||||
{posts?.length === 0 && posts && (
|
||||
<p>
|
||||
No posts found. Create one{" "}
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
import styles from "./list-item.module.css"
|
||||
import Card from "@components/card"
|
||||
import Skeleton from "@components/skeleton"
|
||||
import { Divider, Grid, Spacer } from "@geist-ui/core/dist"
|
||||
|
||||
const ListItemSkeleton = () => (
|
||||
<Card>
|
||||
<Spacer height={1 / 2} />
|
||||
<Grid.Container justify={"space-between"} marginBottom={1 / 2}>
|
||||
<Grid xs={8} paddingLeft={1 / 2}>
|
||||
<Skeleton width={150} />
|
||||
</Grid>
|
||||
<Grid xs={7}>
|
||||
<Skeleton width={100} />
|
||||
</Grid>
|
||||
<Grid xs={4}>
|
||||
<Skeleton width={70} />
|
||||
</Grid>
|
||||
</Grid.Container>
|
||||
export const ListItemSkeleton = () => (
|
||||
<li>
|
||||
<Card style={{ overflowY: "scroll" }}>
|
||||
<>
|
||||
<div className={styles.title}>
|
||||
{/* title */}
|
||||
<Skeleton width={80} height={32} />
|
||||
</div>
|
||||
|
||||
<Divider h="1px" my={0} />
|
||||
<Skeleton width={200} />
|
||||
</Card>
|
||||
<div className={styles.badges}>
|
||||
<Skeleton width={30} height={32} />
|
||||
</div>
|
||||
</>
|
||||
<hr />
|
||||
<Skeleton width={100} height={32} />
|
||||
</Card>
|
||||
</li>
|
||||
)
|
||||
|
||||
export default ListItemSkeleton
|
||||
|
|
3
client/app/components/spinner/index.tsx
Normal file
3
client/app/components/spinner/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import styles from './spinner.module.css'
|
||||
|
||||
export const Spinner = () => <div className={styles.spinner} />
|
17
client/app/components/spinner/spinner.module.css
Normal file
17
client/app/components/spinner/spinner.module.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.spinner {
|
||||
border: 4px solid var(--light-gray);
|
||||
border-top: 4px solid var(--gray);
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
69
client/app/components/toasts/index.tsx
Normal file
69
client/app/components/toasts/index.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
"use client"
|
||||
|
||||
import Toast, { Toaster } from "react-hot-toast"
|
||||
|
||||
export type ToastType = "success" | "error" | "loading" | "default"
|
||||
|
||||
export type ToastProps = {
|
||||
id?: string
|
||||
type: ToastType
|
||||
message: string
|
||||
duration?: number
|
||||
icon?: string
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
loading?: boolean
|
||||
loadingProgress?: number
|
||||
}
|
||||
|
||||
export const useToasts = () => {
|
||||
const setToast = (toast: ToastProps) => {
|
||||
const { type, message, ...rest } = toast
|
||||
if (toast.id) {
|
||||
Toast.dismiss(toast.id)
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "success":
|
||||
Toast.success(message, rest)
|
||||
break
|
||||
case "error":
|
||||
Toast.error(message, rest)
|
||||
break
|
||||
case "loading":
|
||||
Toast.loading(message, rest)
|
||||
break
|
||||
default:
|
||||
Toast(message, rest)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return { setToast }
|
||||
}
|
||||
|
||||
export const Toasts = () => {
|
||||
return (
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
error: {
|
||||
style: {
|
||||
background: "var(--warning)",
|
||||
color: "#fff"
|
||||
}
|
||||
},
|
||||
success: {
|
||||
style: {
|
||||
background: "var(--light-gray)",
|
||||
color: "var(--fg)"
|
||||
}
|
||||
},
|
||||
iconTheme: {
|
||||
primary: "var(--fg)",
|
||||
secondary: "var(--bg)"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -4,7 +4,6 @@ import styles from "@styles/Home.module.css"
|
|||
import { getSession } from "@lib/server/session"
|
||||
import ThemeProvider from "@components/theme/ThemeProvider"
|
||||
import { THEME_COOKIE_NAME } from "@components/theme/theme"
|
||||
import { useServerTheme } from "@components/theme/ThemeServerContextProvider"
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: React.ReactNode
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
import Header from "@components/header"
|
||||
import Page from "@components/page"
|
||||
import { Toasts } from "@components/toasts"
|
||||
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
||||
import { Toaster } from "react-hot-toast"
|
||||
|
||||
export function LayoutWrapper({
|
||||
children,
|
||||
|
@ -15,6 +17,7 @@ export function LayoutWrapper({
|
|||
}) {
|
||||
return (
|
||||
<RadixTooltip.Provider delayDuration={200}>
|
||||
<Toasts />
|
||||
<Page>
|
||||
<Header isAdmin={isAdmin} signedIn={signedIn} />
|
||||
{children}
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { Input, Button, useToasts } from "@geist-ui/core/dist"
|
||||
import { useState } from "react"
|
||||
|
||||
const Password = () => {
|
||||
const [password, setPassword] = useState<string>("")
|
||||
const [newPassword, setNewPassword] = useState<string>("")
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>("")
|
||||
|
||||
const { setToast } = useToasts()
|
||||
|
||||
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
|
||||
const handleNewPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNewPassword(e.target.value)
|
||||
}
|
||||
|
||||
const handleConfirmPasswordChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setConfirmPassword(e.target.value)
|
||||
}
|
||||
|
||||
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
if (!password || !newPassword || !confirmPassword) {
|
||||
setToast({
|
||||
text: "Please fill out all fields",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
setToast({
|
||||
text: "New password and confirm password do not match",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
|
||||
const res = await fetch("/server-api/auth/change-password", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
oldPassword: password,
|
||||
newPassword
|
||||
})
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
setToast({
|
||||
text: "Password updated successfully",
|
||||
type: "success"
|
||||
})
|
||||
setPassword("")
|
||||
setNewPassword("")
|
||||
setConfirmPassword("")
|
||||
} else {
|
||||
const data = await res.json()
|
||||
|
||||
setToast({
|
||||
text: data.error ?? "Failed to update password",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "var(--gap)",
|
||||
maxWidth: "300px"
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div>
|
||||
<label htmlFor="current-password">Current password</label>
|
||||
<Input
|
||||
onChange={handlePasswordChange}
|
||||
minLength={6}
|
||||
maxLength={128}
|
||||
value={password}
|
||||
id="current-password"
|
||||
htmlType="password"
|
||||
required
|
||||
autoComplete="current-password"
|
||||
placeholder="Current Password"
|
||||
width={"100%"}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="new-password">New password</label>
|
||||
<Input
|
||||
onChange={handleNewPasswordChange}
|
||||
minLength={6}
|
||||
maxLength={128}
|
||||
value={newPassword}
|
||||
id="new-password"
|
||||
htmlType="password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
placeholder="New Password"
|
||||
width={"100%"}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="confirm-password">Confirm password</label>
|
||||
<Input
|
||||
onChange={handleConfirmPasswordChange}
|
||||
minLength={6}
|
||||
maxLength={128}
|
||||
value={confirmPassword}
|
||||
id="confirm-password"
|
||||
htmlType="password"
|
||||
required
|
||||
autoComplete="confirm-password"
|
||||
placeholder="Confirm Password"
|
||||
width={"100%"}
|
||||
/>
|
||||
</div>
|
||||
<Button htmlType="submit" auto>
|
||||
Change Password
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default Password
|
|
@ -93,7 +93,7 @@ const Profile = ({ user }: { user: User }) => {
|
|||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{/* <div>
|
||||
<label htmlFor="bio">Biography (max 250 characters)</label>
|
||||
<textarea
|
||||
id="bio"
|
||||
|
@ -103,8 +103,8 @@ const Profile = ({ user }: { user: User }) => {
|
|||
value={bio}
|
||||
onChange={handleBioChange}
|
||||
/>
|
||||
</div>
|
||||
<Button htmlType="submit" auto>
|
||||
</div> */}
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
--gray-alpha: rgba(255, 255, 255, 0.5);
|
||||
--selection: rgba(255, 255, 255, 0.99);
|
||||
--border: var(--lighter-gray);
|
||||
--warning: rgb(27, 134, 23);
|
||||
--warning: #ff6700;
|
||||
--link: #3291ff;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
@ -78,8 +78,9 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// TODO: replace this with an accessible alternative
|
||||
*:focus-visible {
|
||||
outline: 1px solid var(--gray);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
|
@ -179,3 +180,8 @@ textarea {
|
|||
padding: var(--gap-half);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
border-color: var(--light-gray);
|
||||
outline: none;
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
"@next-auth/prisma-adapter": "^1.0.5",
|
||||
"@next/eslint-plugin-next": "13.0.5-canary.3",
|
||||
"@prisma/client": "^4.6.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.1",
|
||||
"@radix-ui/react-popover": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.1",
|
||||
"@radix-ui/react-tooltip": "^1.0.2",
|
||||
|
@ -26,8 +27,8 @@
|
|||
"client-zip": "2.2.1",
|
||||
"cookies-next": "^2.1.1",
|
||||
"jest": "^29.3.1",
|
||||
"next": "13.0.5-canary.3",
|
||||
"next-auth": "^4.16.4",
|
||||
"next": "13.0.6-canary.1",
|
||||
"next-auth": "^4.17.0",
|
||||
"rc-table": "7.24.1",
|
||||
"react": "18.2.0",
|
||||
"react-datepicker": "4.8.0",
|
||||
|
|
|
@ -7,6 +7,7 @@ specifiers:
|
|||
'@next/bundle-analyzer': 12.1.6
|
||||
'@next/eslint-plugin-next': 13.0.5-canary.3
|
||||
'@prisma/client': ^4.6.1
|
||||
'@radix-ui/react-dropdown-menu': ^2.0.1
|
||||
'@radix-ui/react-popover': ^1.0.2
|
||||
'@radix-ui/react-tabs': ^1.0.1
|
||||
'@radix-ui/react-tooltip': ^1.0.2
|
||||
|
@ -25,8 +26,8 @@ specifiers:
|
|||
eslint-config-next: 13.0.3
|
||||
jest: ^29.3.1
|
||||
katex: ^0.16.3
|
||||
next: 13.0.5-canary.3
|
||||
next-auth: ^4.16.4
|
||||
next: 13.0.6-canary.1
|
||||
next-auth: ^4.17.0
|
||||
next-unused: 0.0.6
|
||||
prettier: 2.6.2
|
||||
prisma: ^4.6.1
|
||||
|
@ -47,9 +48,10 @@ specifiers:
|
|||
dependencies:
|
||||
'@geist-ui/core': 2.3.8_biqbaboplfbrettd7655fr4n2y
|
||||
'@geist-ui/icons': 1.0.2_zhza2kbnl2wkkf7vqdl3ton2f4
|
||||
'@next-auth/prisma-adapter': 1.0.5_2pl3b2nwmjya7el2zbe6cwkney
|
||||
'@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
|
||||
'@radix-ui/react-dropdown-menu': 2.0.1_jbvntnid6ohjelon6ccj5dhg2u
|
||||
'@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
||||
'@radix-ui/react-tabs': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
||||
|
@ -58,8 +60,8 @@ dependencies:
|
|||
client-zip: 2.2.1
|
||||
cookies-next: 2.1.1
|
||||
jest: 29.3.1_@types+node@17.0.23
|
||||
next: 13.0.5-canary.3_biqbaboplfbrettd7655fr4n2y
|
||||
next-auth: 4.16.4_p6ldyhl3eilwbxhfzedqikw7ni
|
||||
next: 13.0.6-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||
next-auth: 4.17.0_cejjzyjft5qpe7pbv5t5jzassa
|
||||
rc-table: 7.24.1_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
|
||||
|
@ -810,14 +812,14 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@next-auth/prisma-adapter/1.0.5_2pl3b2nwmjya7el2zbe6cwkney:
|
||||
/@next-auth/prisma-adapter/1.0.5_o53gfpk3vz2btjrokqfjjwwn3m:
|
||||
resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==}
|
||||
peerDependencies:
|
||||
'@prisma/client': '>=2.26.0 || >=3'
|
||||
next-auth: ^4
|
||||
dependencies:
|
||||
'@prisma/client': 4.6.1_prisma@4.6.1
|
||||
next-auth: 4.16.4_p6ldyhl3eilwbxhfzedqikw7ni
|
||||
next-auth: 4.17.0_cejjzyjft5qpe7pbv5t5jzassa
|
||||
dev: false
|
||||
|
||||
/@next/bundle-analyzer/12.1.6:
|
||||
|
@ -829,8 +831,8 @@ packages:
|
|||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@next/env/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-x8hWJis9ICO6y5e7BNBPjFmFvgDRe5URd5YV3qgkw1flCRDLL3QtfTLig6nXH9DaBHaQ1a0ej7vvlzyDq6iHEg==}
|
||||
/@next/env/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-L88vP2GvU2NvF5YSIDjqYzzJQRNDg3F08qE/pTLClsYXLusRWAJ1lgI9sZFqLrMbZuj2xd0hWYyYjCrrg/LLLw==}
|
||||
dev: false
|
||||
|
||||
/@next/eslint-plugin-next/13.0.3:
|
||||
|
@ -845,8 +847,8 @@ packages:
|
|||
glob: 7.1.7
|
||||
dev: false
|
||||
|
||||
/@next/swc-android-arm-eabi/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-t/HW4YMqsXMnyaOvgK4U5wue8sgV6+yjp7eNKVqLCDxMdXemLUIqPTdVOdfU2oVScUNYj5VQFWO52HE/x6LugQ==}
|
||||
/@next/swc-android-arm-eabi/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-3/ZSIsm2yYZXUyqupAri4+6J97wVdHIP2yrcTLoKdYQKm1lp1z5hIZLtxQCV4vLyYNlNIafvIqeasfYWuYzCgw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
@ -854,8 +856,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-WLNjvoC9sDWqDgYOJDEPKeYwS/tymTvUDBDzQfGyl0XOW8g01wD4S1TBlHDP+D9y85BxLdwlUJoYbOwhAfwI0A==}
|
||||
/@next/swc-android-arm64/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-/J2EXT8L82HgV8sf2SzSdpTuT5gtpPGgekPRCF1ttYyN8FAR/VHPKOLYTbKz3NQ95ogttrW28RFPsMSekQpLaQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
@ -863,8 +865,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-JeGAnktTy7fQeF8PrQJgT25LlNnRFiH6zXWVfHTRk4ufcERifaOnwpzPLjIxF6euo3qD8VynCQhYH/JsVS5HNQ==}
|
||||
/@next/swc-darwin-arm64/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-n+IxXIJSEm3HdumauULSZCehyKyQ0EfWU/qf5oMjyGOQ/sHQfgT5sGYtfnnNvvhSlVgFhyz7MalPRwJQWAIIZQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
@ -872,8 +874,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-yehsw+60uE47but0FUJD2+/06tQSsIm1/XFlFFhHv4d+bq39GB2fT+wL+h/aLK+es0yY7rx1+6Yad7CQ7a2J+Q==}
|
||||
/@next/swc-darwin-x64/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-0gQ2zLBFJT8rO080APvY/fHxjSIRVD7VpF0C12BXroFJ3XMnCoa6scniVgKwV4pjhTtlJoLSk3y4wNITyouLDQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
@ -881,8 +883,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-freebsd-x64/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-Nd1RicUhogxTsassJNGG+4ha5ezyjJb58J6Vjs9kT5I40Iu8Mw98hCwn6VmiCtJMpY4HGJcuYtCYyXh5hY6ByQ==}
|
||||
/@next/swc-freebsd-x64/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-zAnm1JtxgrNkjiBef7U8lO9JOqNhy13mKA0Z27XCeAunhaar0hNZNHnKUlISVhAZovpHs96fp0mm7Skw0WCkpA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
@ -890,8 +892,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-t8TNq1rVBaYJz5dzAJIXgfYh7KRFich6gIT38zJ82McbKWFccuY6+9+FP6n0zkNFAZAd0CBvTQJ8fQFX6XPMog==}
|
||||
/@next/swc-linux-arm-gnueabihf/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-JJCdGBEeNzGXm0AQUfVY9x3C3WvxCcawZ1tCYYkOqLD21Dj5gDTNSZT2cAGFDovfyn5hN/Vltf20Zs8ilUPr1w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
@ -899,8 +901,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-jsHOBJSkBn247KlW2BG5j1hoLG3EIa1gE/FLCj7w/cQBCtrhb83oGBxat52Wh7UmwajrvNl13clQV0JlmkX3sg==}
|
||||
/@next/swc-linux-arm64-gnu/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-oE9D+Mm240fRpZWZBhFiztmJ/D7uwQ3jfOZcQVf1aN3P+9De+5jyv2TKoHrjrRh5xjloTEDIpt2wNKimlwZvkg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -908,8 +910,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-AVsfjFzRKx9I4rbHBOK1WZyl3jF635uLcb7ssrJtxyMGHcr4R9KCTcl+kv7RZ2WNKVafun0pUAITKHoqKVO4fg==}
|
||||
/@next/swc-linux-arm64-musl/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-+DukK2LdoZo4Ds0o2XGAC9324O9auxvHMF4MwDzrXrherBFEeSZUDIxcz4zu5/pd8dyQ8JY9nwILJEevla23pA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -917,8 +919,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-E83c9cs+8hwvpTUcn991ec/Q6tDG54SzvVrvNqAj23yiWGfHx0MDt1NqYtF0WOJ7W+6G6PtlD9SR0KKiaPMSeA==}
|
||||
/@next/swc-linux-x64-gnu/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-1bULat6aif5E48s23lAh4vmuVogxz3arXMawlqHwHqwJWpzxF2vyAl3Ilh4mXJnD1xvwiGRH7Aov7HMQJhF2+g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -926,8 +928,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-d9CT6qNDxf/C2j8oU6sb2XK2C0qE5N6uI+JL2r19fZKgQIgjCMEWbuyI2oVpq/LrwJ4w0vmjxuRg/9YEb8zX9w==}
|
||||
/@next/swc-linux-x64-musl/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-LN9qXV3Rh1jMcaSXg5DGZYbN6nVCn6A/aQBTw4K5DWu7bHTiEkq4d7/OZN+gfJcqW8BuRtG4zLeraae/1RO9pw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -935,8 +937,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-4Mcw9pc8HgZ0FoOybyfDW17dXXgJnqs8TDXbWDlV9tGrbYJ5yPPe2r0gZaPCGCtbYLLz3XQfd6kDDf2J0hx7Kw==}
|
||||
/@next/swc-win32-arm64-msvc/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-792QCYO+lF4N1c2bwYVwhLwMJMhnmvQRVOnCmRlGhbaZJ0f6ZuIYCxjqAqV8Por+u0ErenRMv5BGleK8wHfQJA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
@ -944,8 +946,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-vGHnW+l+aE264lmz7warC7FPEUb3RDypooKZHDb7fTtlbqnEBf8MVlPYLVj73MSwbW8vKfoatScCG09tljYztA==}
|
||||
/@next/swc-win32-ia32-msvc/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-hwL9JjT7C1ZP+Wj7F4QIoewEErqc70Ax0aeKqQZdKU57djmIWvvvnyh9p/uUQFHLc3M4Um+XvtqpyFZB6RcYbg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
@ -953,8 +955,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/13.0.5-canary.3:
|
||||
resolution: {integrity: sha512-BvqxBWnY2D0DDBh+NxSQ70+f/kLEEQAbkbyjOcKIxjtYY9Gu6FHAHHyyTNwXMRKr7X3yiX/olLTy1EekCZ0M6A==}
|
||||
/@next/swc-win32-x64-msvc/13.0.6-canary.1:
|
||||
resolution: {integrity: sha512-KA6/9W7ZEfkN/ecH8f/fg+WyQaJnRG/UE2IKOdQHbL4uVtHJk5PQmYT+nBZFVnbNHJB2ouNmL4pFJM/7JjNBww==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
@ -1092,6 +1094,26 @@ packages:
|
|||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dropdown-menu/2.0.1_jbvntnid6ohjelon6ccj5dhg2u:
|
||||
resolution: {integrity: sha512-WDZqmwsNuxdBMkvgB85UeSPAT0wSBd+ojxtzX7lU7uYYh47gacCj6Spo0l9+X4TMe3JA1BBMN9c7OhIMaQeKbg==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-context': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-id': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-menu': 2.0.1_jbvntnid6ohjelon6ccj5dhg2u
|
||||
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-guards/1.0.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==}
|
||||
peerDependencies:
|
||||
|
@ -1125,6 +1147,37 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-menu/2.0.1_jbvntnid6ohjelon6ccj5dhg2u:
|
||||
resolution: {integrity: sha512-I5FFZQxCl2fHoJ7R0m5/oWA9EX8/ttH4AbgneoCH7DAXQioFeb0XMAYnOVSp1GgJZ1Nx/mohxNQSeTMcaF1YPw==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-collection': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-context': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-direction': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-dismissable-layer': 1.0.2_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-focus-guards': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-focus-scope': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-id': 1.0.0_react@18.2.0
|
||||
'@radix-ui/react-popper': 1.0.1_jbvntnid6ohjelon6ccj5dhg2u
|
||||
'@radix-ui/react-portal': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-roving-focus': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@radix-ui/react-slot': 1.0.1_react@18.2.0
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0
|
||||
aria-hidden: 1.2.1_ulhmhxukhxjgxaybrsjlob7ffu
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-remove-scroll: 2.5.5_ulhmhxukhxjgxaybrsjlob7ffu
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popover/1.0.2_jbvntnid6ohjelon6ccj5dhg2u:
|
||||
resolution: {integrity: sha512-4tqZEl9w95R5mlZ/sFdgBnfhCBOEPepLIurBA5kt/qaAhldJ1tNQd0ngr0ET0AHbPotT4mwxMPr7a+MA/wbK0g==}
|
||||
peerDependencies:
|
||||
|
@ -5161,8 +5214,8 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/next-auth/4.16.4_p6ldyhl3eilwbxhfzedqikw7ni:
|
||||
resolution: {integrity: sha512-KXW578+ER1u5RcWLwCHMdb/RIBIO6JM8r6xlf9RIPSKzkvDcX9FHiZfJS2vOq/SurHXPJZc4J3OS4IDJpF74Dw==}
|
||||
/next-auth/4.17.0_cejjzyjft5qpe7pbv5t5jzassa:
|
||||
resolution: {integrity: sha512-aN2tdnjS0MDeUpB2tBDOaWnegkgeMWrsccujbXRGMJ607b+EwRcy63MFGSr0OAboDJEe0902piXQkt94GqF8Qw==}
|
||||
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0}
|
||||
peerDependencies:
|
||||
next: ^12.2.5 || ^13
|
||||
|
@ -5177,7 +5230,7 @@ packages:
|
|||
'@panva/hkdf': 1.0.2
|
||||
cookie: 0.5.0
|
||||
jose: 4.11.0
|
||||
next: 13.0.5-canary.3_biqbaboplfbrettd7655fr4n2y
|
||||
next: 13.0.6-canary.1_biqbaboplfbrettd7655fr4n2y
|
||||
oauth: 0.9.15
|
||||
openid-client: 5.3.0
|
||||
preact: 10.11.2
|
||||
|
@ -5198,8 +5251,8 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/next/13.0.5-canary.3_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-u3As6SkLXf2u9Mt06B3gBdQzVi45qoH27fyj2UGH+GcWjloz0x0Q+99CEz0sr93zS6iQRZuDn93PMZXoXIa8Og==}
|
||||
/next/13.0.6-canary.1_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-aLHWxU8tMcsVKyStTQvfATZ8TzX/X/VbfV1erVn+pF4uI5+ETvAGWO1geo1FrLzrW+xB/h+9UPAUiqh21oVZEA==}
|
||||
engines: {node: '>=14.6.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
@ -5216,28 +5269,27 @@ packages:
|
|||
sass:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@next/env': 13.0.5-canary.3
|
||||
'@next/env': 13.0.6-canary.1
|
||||
'@swc/helpers': 0.4.14
|
||||
caniuse-lite: 1.0.30001431
|
||||
postcss: 8.4.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
styled-jsx: 5.1.0_react@18.2.0
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
optionalDependencies:
|
||||
'@next/swc-android-arm-eabi': 13.0.5-canary.3
|
||||
'@next/swc-android-arm64': 13.0.5-canary.3
|
||||
'@next/swc-darwin-arm64': 13.0.5-canary.3
|
||||
'@next/swc-darwin-x64': 13.0.5-canary.3
|
||||
'@next/swc-freebsd-x64': 13.0.5-canary.3
|
||||
'@next/swc-linux-arm-gnueabihf': 13.0.5-canary.3
|
||||
'@next/swc-linux-arm64-gnu': 13.0.5-canary.3
|
||||
'@next/swc-linux-arm64-musl': 13.0.5-canary.3
|
||||
'@next/swc-linux-x64-gnu': 13.0.5-canary.3
|
||||
'@next/swc-linux-x64-musl': 13.0.5-canary.3
|
||||
'@next/swc-win32-arm64-msvc': 13.0.5-canary.3
|
||||
'@next/swc-win32-ia32-msvc': 13.0.5-canary.3
|
||||
'@next/swc-win32-x64-msvc': 13.0.5-canary.3
|
||||
'@next/swc-android-arm-eabi': 13.0.6-canary.1
|
||||
'@next/swc-android-arm64': 13.0.6-canary.1
|
||||
'@next/swc-darwin-arm64': 13.0.6-canary.1
|
||||
'@next/swc-darwin-x64': 13.0.6-canary.1
|
||||
'@next/swc-freebsd-x64': 13.0.6-canary.1
|
||||
'@next/swc-linux-arm-gnueabihf': 13.0.6-canary.1
|
||||
'@next/swc-linux-arm64-gnu': 13.0.6-canary.1
|
||||
'@next/swc-linux-arm64-musl': 13.0.6-canary.1
|
||||
'@next/swc-linux-x64-gnu': 13.0.6-canary.1
|
||||
'@next/swc-linux-x64-musl': 13.0.6-canary.1
|
||||
'@next/swc-win32-arm64-msvc': 13.0.6-canary.1
|
||||
'@next/swc-win32-ia32-msvc': 13.0.6-canary.1
|
||||
'@next/swc-win32-x64-msvc': 13.0.6-canary.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
@ -7100,14 +7152,6 @@ packages:
|
|||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/use-sync-external-store/1.2.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
|
|
Loading…
Reference in a new issue