title: don't re-render when updating post
This commit is contained in:
parent
8fe7299258
commit
881e693e76
18 changed files with 79 additions and 70 deletions
|
@ -59,14 +59,10 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardContent {
|
.chevron {
|
||||||
display: flex;
|
transition: transform 0.2s ease-in-out;
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: var(--gap-half);
|
|
||||||
padding-top: 200px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 82rem) {
|
[data-state="open"] .chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,8 +49,11 @@ const FileDropdown = ({ files }: { files: File[] }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<Popover.Trigger className={clsx(buttonStyles.button)}>
|
<Popover.Trigger className={buttonStyles.button}>
|
||||||
<div className={buttonStyles.icon}>
|
<div
|
||||||
|
className={clsx(buttonStyles.icon, styles.chevron)}
|
||||||
|
style={{ marginRight: 6 }}
|
||||||
|
>
|
||||||
<ChevronDown />
|
<ChevronDown />
|
||||||
</div>
|
</div>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Note, Text } from "@geist-ui/core/dist"
|
import Note from "@components/note"
|
||||||
|
|
||||||
export default function ExpiredPage() {
|
export default function ExpiredPage() {
|
||||||
return (
|
return (
|
||||||
<Note type="error" label={false}>
|
<Note type="error">
|
||||||
<h2>Error: The Drift you're trying to view has expired.</h2>
|
<strong>Error:</strong> The Drift you're trying to view has expired.
|
||||||
</Note>
|
</Note>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,4 @@ const Description = ({ onChange, description }: props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(Description)
|
export default Description
|
||||||
|
|
|
@ -6,9 +6,17 @@
|
||||||
.actionWrapper .actions {
|
.actionWrapper .actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 4px;
|
top: -34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* small screens, top: 0 */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.actionWrapper .actions {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.actionWrapper .actions {
|
.actionWrapper .actions {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -41,4 +41,4 @@ const DocumentList = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(DocumentList)
|
export default DocumentList
|
||||||
|
|
|
@ -164,13 +164,18 @@ const Post = ({
|
||||||
|
|
||||||
const onChangeExpiration = (date: Date) => setExpiresAt(date)
|
const onChangeExpiration = (date: Date) => setExpiresAt(date)
|
||||||
|
|
||||||
const onChangeTitle = (e: ChangeEvent<HTMLInputElement>) => {
|
const onChangeTitle = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
setTitle(e.target.value)
|
setTitle(e.target.value)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const onChangeDescription = (e: ChangeEvent<HTMLInputElement>) => {
|
const onChangeDescription = useCallback(
|
||||||
setDescription(e.target.value)
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
}
|
e.preventDefault()
|
||||||
|
setDescription(e.target.value)
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
const updateDocTitle = (i: number) => (title: string) => {
|
const updateDocTitle = (i: number) => (title: string) => {
|
||||||
setDocs((docs) =>
|
setDocs((docs) =>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ChangeEvent } from "react"
|
import { ChangeEvent, memo } from "react"
|
||||||
|
|
||||||
import Input from "@components/input"
|
import Input from "@components/input"
|
||||||
import styles from "./title.module.css"
|
import styles from "./title.module.css"
|
||||||
|
@ -37,4 +37,4 @@ const Title = ({ onChange, title }: props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Title
|
export default memo(Title)
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
.actionWrapper {
|
.actionWrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionWrapper .actions {
|
.actionWrapper .actions {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import PostPage from "app/(posts)/post/[id]/components/post-page"
|
import PostPage from "app/(posts)/post/[id]/components/post-page"
|
||||||
import { notFound } from "next/navigation"
|
import { notFound, redirect } from "next/navigation"
|
||||||
import { getPostById, Post } from "@lib/server/prisma"
|
import { getPostById, Post } from "@lib/server/prisma"
|
||||||
import { getCurrentUser } from "@lib/server/session"
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ const getPost = async (id: string) => {
|
||||||
if (post.expiresAt && !isAuthorOrAdmin) {
|
if (post.expiresAt && !isAuthorOrAdmin) {
|
||||||
const expirationDate = new Date(post.expiresAt)
|
const expirationDate = new Date(post.expiresAt)
|
||||||
if (expirationDate < new Date()) {
|
if (expirationDate < new Date()) {
|
||||||
return notFound()
|
return redirect("/expired")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { Page } from "@geist-ui/core/dist"
|
|
||||||
|
|
||||||
const Error = ({ status }: { status: number }) => {
|
|
||||||
return (
|
|
||||||
<Page title={status.toString() || "Error"}>
|
|
||||||
{status === 404 ? (
|
|
||||||
<h1>This page cannot be found.</h1>
|
|
||||||
) : (
|
|
||||||
<section>
|
|
||||||
<p>An error occurred: {status}</p>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</Page>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Error
|
|
|
@ -9,7 +9,6 @@ const Note = ({
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
} & React.ComponentProps<"div">) => (
|
} & React.ComponentProps<"div">) => (
|
||||||
<div className={`${styles.note} ${styles[type]}`} {...props}>
|
<div className={`${styles.note} ${styles[type]}`} {...props}>
|
||||||
<strong className={styles.type}>{type}:</strong>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.note {
|
.note {
|
||||||
font-size: 0.8em;
|
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: var(--gap);
|
padding: var(--gap);
|
||||||
|
@ -20,8 +19,14 @@
|
||||||
background: #f33;
|
background: #f33;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .warning,
|
||||||
|
[data-theme="light"] .error {
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
.type {
|
.type {
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
font-size: initial;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Card from "@components/card"
|
import Card from "@components/card"
|
||||||
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
||||||
import "./tooltip.css"
|
import styles from "./tooltip.module.css"
|
||||||
|
|
||||||
const Tooltip = ({
|
const Tooltip = ({
|
||||||
children,
|
children,
|
||||||
|
@ -17,9 +17,8 @@ const Tooltip = ({
|
||||||
<RadixTooltip.Trigger asChild className={className}>
|
<RadixTooltip.Trigger asChild className={className}>
|
||||||
{children}
|
{children}
|
||||||
</RadixTooltip.Trigger>
|
</RadixTooltip.Trigger>
|
||||||
|
|
||||||
<RadixTooltip.Content>
|
<RadixTooltip.Content>
|
||||||
<Card className="tooltip">{content}</Card>
|
<Card className={styles.tooltip}>{content}</Card>
|
||||||
</RadixTooltip.Content>
|
</RadixTooltip.Content>
|
||||||
</RadixTooltip.Root>
|
</RadixTooltip.Root>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
/* animate */
|
|
||||||
animation: fadein 300ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
24
client/app/components/tooltip/tooltip.module.css
Normal file
24
client/app/components/tooltip/tooltip.module.css
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
.tooltip {
|
||||||
|
animation: fadein 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-side='top'] .tooltip{
|
||||||
|
margin-bottom: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip[data-side='bottom'] {
|
||||||
|
margin-top: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip[data-side='left'] {
|
||||||
|
margin-right: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip[data-side='right'] {
|
||||||
|
margin-left: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Note, Input, Textarea, Button, useToasts } from "@geist-ui/core/dist"
|
import Button from "@components/button"
|
||||||
|
import Input from "@components/input"
|
||||||
|
import Note from "@components/note"
|
||||||
|
import { useToasts } from "@geist-ui/core/dist"
|
||||||
import { User } from "next-auth"
|
import { User } from "next-auth"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
|
||||||
|
@ -57,7 +60,7 @@ const Profile = ({ user }: { user: User }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Note type="warning" marginBottom={"var(--gap)"}>
|
<Note type="warning">
|
||||||
This information will be publicly available on your profile
|
This information will be publicly available on your profile
|
||||||
</Note>
|
</Note>
|
||||||
<form
|
<form
|
||||||
|
@ -83,7 +86,7 @@ const Profile = ({ user }: { user: User }) => {
|
||||||
<label htmlFor="email">Email</label>
|
<label htmlFor="email">Email</label>
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
htmlType="email"
|
type="email"
|
||||||
width={"100%"}
|
width={"100%"}
|
||||||
placeholder="my@email.io"
|
placeholder="my@email.io"
|
||||||
value={user.email || undefined}
|
value={user.email || undefined}
|
||||||
|
@ -92,12 +95,12 @@ const Profile = ({ user }: { user: User }) => {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="bio">Biography (max 250 characters)</label>
|
<label htmlFor="bio">Biography (max 250 characters)</label>
|
||||||
<Textarea
|
<textarea
|
||||||
id="bio"
|
id="bio"
|
||||||
width="100%"
|
style={{ width: "100%" }}
|
||||||
maxLength={250}
|
maxLength={250}
|
||||||
placeholder="I enjoy..."
|
placeholder="I enjoy..."
|
||||||
value={bio || ""}
|
value={bio}
|
||||||
onChange={handleBioChange}
|
onChange={handleBioChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -179,10 +179,3 @@ textarea {
|
||||||
padding: var(--gap-half);
|
padding: var(--gap-half);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue