client/server: linting and fix next building
This commit is contained in:
parent
bceeb5cee8
commit
a52e9a1c62
17 changed files with 368 additions and 295 deletions
|
@ -9,10 +9,10 @@ type Action = {
|
|||
const ActionDropdown = ({
|
||||
title = "Actions",
|
||||
actions,
|
||||
showTitle = false,
|
||||
showTitle = false
|
||||
}: {
|
||||
title?: string,
|
||||
showTitle?: boolean,
|
||||
title?: string
|
||||
showTitle?: boolean
|
||||
actions: Action[]
|
||||
}) => {
|
||||
return (
|
||||
|
@ -20,14 +20,9 @@ const ActionDropdown = ({
|
|||
title={title}
|
||||
content={
|
||||
<>
|
||||
{showTitle && <Popover.Item title>
|
||||
{title}
|
||||
</Popover.Item>}
|
||||
{actions.map(action => (
|
||||
<Popover.Item
|
||||
onClick={action.onClick}
|
||||
key={action.title}
|
||||
>
|
||||
{showTitle && <Popover.Item title>{title}</Popover.Item>}
|
||||
{actions.map((action) => (
|
||||
<Popover.Item onClick={action.onClick} key={action.title}>
|
||||
{action.title}
|
||||
</Popover.Item>
|
||||
))}
|
||||
|
|
|
@ -109,14 +109,14 @@ const PostTable = () => {
|
|||
dataIndex: "",
|
||||
key: "actions",
|
||||
width: 50,
|
||||
render(post: Post) {
|
||||
render() {
|
||||
return (
|
||||
<ActionDropdown
|
||||
title="Actions"
|
||||
actions={[
|
||||
{
|
||||
title: "Delete",
|
||||
onClick: () => deletePost(post.id)
|
||||
onClick: () => deletePost()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
@ -128,7 +128,11 @@ const PostTable = () => {
|
|||
return (
|
||||
<SettingsGroup title="Posts">
|
||||
{!posts && <Fieldset.Subtitle>Loading...</Fieldset.Subtitle>}
|
||||
{posts && <Fieldset.Subtitle><h5>{posts.length} posts</h5></Fieldset.Subtitle>}
|
||||
{posts && (
|
||||
<Fieldset.Subtitle>
|
||||
<h5>{posts.length} posts</h5>
|
||||
</Fieldset.Subtitle>
|
||||
)}
|
||||
{posts && <Table columns={tableColumns} data={tablePosts} />}
|
||||
</SettingsGroup>
|
||||
)
|
||||
|
|
|
@ -131,7 +131,8 @@ const UserTable = () => {
|
|||
actions={[
|
||||
{
|
||||
title: user.role === "admin" ? "Change role" : "Make admin",
|
||||
onClick: () => toggleRole(user.id, user.role === "admin" ? "user" : "admin")
|
||||
onClick: () =>
|
||||
toggleRole(user.id, user.role === "admin" ? "user" : "admin")
|
||||
},
|
||||
{
|
||||
title: "Delete",
|
||||
|
@ -147,7 +148,11 @@ const UserTable = () => {
|
|||
return (
|
||||
<SettingsGroup title="Users">
|
||||
{!users && <Fieldset.Subtitle>Loading...</Fieldset.Subtitle>}
|
||||
{users && <Fieldset.Subtitle><h5>{users.length} users</h5></Fieldset.Subtitle>}
|
||||
{users && (
|
||||
<Fieldset.Subtitle>
|
||||
<h5>{users.length} users</h5>
|
||||
</Fieldset.Subtitle>
|
||||
)}
|
||||
{users && <Table columns={usernameColumns} data={tableUsers} />}
|
||||
</SettingsGroup>
|
||||
)
|
||||
|
|
|
@ -89,10 +89,10 @@ const Header = () => {
|
|||
href: "/mine"
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
name: "settings",
|
||||
icon: <SettingsIcon />,
|
||||
value: 'settings',
|
||||
href: '/settings'
|
||||
value: "settings",
|
||||
href: "/settings"
|
||||
},
|
||||
{
|
||||
name: "sign out",
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
Button,
|
||||
useToasts,
|
||||
ButtonDropdown,
|
||||
Input,
|
||||
} from "@geist-ui/core"
|
||||
import { Button, useToasts, ButtonDropdown, Input } from "@geist-ui/core"
|
||||
import { useRouter } from "next/router"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import generateUUID from "@lib/generate-uuid"
|
||||
|
|
|
@ -97,10 +97,7 @@ const ListItem = ({
|
|||
{post.files?.map((file: File) => {
|
||||
return (
|
||||
<div key={file.id}>
|
||||
<Link
|
||||
color
|
||||
href={`/post/${post.id}#${file.title}`}
|
||||
>
|
||||
<Link color href={`/post/${post.id}#${file.title}`}>
|
||||
{file.title || "Untitled file"}
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -85,9 +85,7 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
|||
}
|
||||
|
||||
const viewParentClick = () => {
|
||||
router.push(
|
||||
`/post/${post.parent!.id}`
|
||||
)
|
||||
router.push(`/post/${post.parent!.id}`)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
|
@ -123,11 +121,7 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
|||
Edit a Copy
|
||||
</Button>
|
||||
{post.parent && (
|
||||
<Button
|
||||
auto
|
||||
icon={<Parent />}
|
||||
onClick={viewParentClick}
|
||||
>
|
||||
<Button auto icon={<Parent />} onClick={viewParentClick}>
|
||||
View Parent
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
import { Fieldset, Text, Divider } from "@geist-ui/core"
|
||||
import styles from './settings-group.module.css'
|
||||
import styles from "./settings-group.module.css"
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
children: React.ReactNode | React.ReactNode[],
|
||||
title: string
|
||||
children: React.ReactNode | React.ReactNode[]
|
||||
}
|
||||
|
||||
const SettingsGroup = ({
|
||||
title,
|
||||
children,
|
||||
}: Props) => {
|
||||
return <Fieldset>
|
||||
const SettingsGroup = ({ title, children }: Props) => {
|
||||
return (
|
||||
<Fieldset>
|
||||
<Fieldset.Content>
|
||||
<Text h4>{title}</Text>
|
||||
</Fieldset.Content>
|
||||
<Divider />
|
||||
<Fieldset.Content className={styles.content}>
|
||||
{children}
|
||||
</Fieldset.Content>
|
||||
<Fieldset.Content className={styles.content}>{children}</Fieldset.Content>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettingsGroup
|
||||
|
|
|
@ -3,12 +3,15 @@ import Profile from "./sections/profile"
|
|||
import SettingsGroup from "../settings-group"
|
||||
|
||||
const SettingsPage = () => {
|
||||
return (<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 'var(--gap)',
|
||||
marginBottom: 'var(--gap)',
|
||||
}}>
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "var(--gap)",
|
||||
marginBottom: "var(--gap)"
|
||||
}}
|
||||
>
|
||||
<h1>Settings</h1>
|
||||
<SettingsGroup title="Profile">
|
||||
<Profile />
|
||||
|
@ -16,7 +19,8 @@ const SettingsPage = () => {
|
|||
<SettingsGroup title="Password">
|
||||
<Password />
|
||||
</SettingsGroup>
|
||||
</div>)
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettingsPage
|
|
@ -3,9 +3,9 @@ import Cookies from "js-cookie"
|
|||
import { useState } from "react"
|
||||
|
||||
const Password = () => {
|
||||
const [password, setPassword] = useState<string>('')
|
||||
const [newPassword, setNewPassword] = useState<string>('')
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>('')
|
||||
const [password, setPassword] = useState<string>("")
|
||||
const [newPassword, setNewPassword] = useState<string>("")
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>("")
|
||||
|
||||
const { setToast } = useToasts()
|
||||
|
||||
|
@ -17,7 +17,9 @@ const Password = () => {
|
|||
setNewPassword(e.target.value)
|
||||
}
|
||||
|
||||
const handleConfirmPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleConfirmPasswordChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setConfirmPassword(e.target.value)
|
||||
}
|
||||
|
||||
|
@ -26,14 +28,14 @@ const Password = () => {
|
|||
if (!password || !newPassword || !confirmPassword) {
|
||||
setToast({
|
||||
text: "Please fill out all fields",
|
||||
type: "error",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
setToast({
|
||||
text: "New password and confirm password do not match",
|
||||
type: "error",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -41,29 +43,28 @@ const Password = () => {
|
|||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${Cookies.get("drift-token")}`,
|
||||
Authorization: `Bearer ${Cookies.get("drift-token")}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
oldPassword: password,
|
||||
newPassword,
|
||||
}),
|
||||
newPassword
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
if (res.status === 200) {
|
||||
setToast({
|
||||
text: "Password updated successfully",
|
||||
type: "success",
|
||||
type: "success"
|
||||
})
|
||||
setPassword('')
|
||||
setNewPassword('')
|
||||
setConfirmPassword('')
|
||||
setPassword("")
|
||||
setNewPassword("")
|
||||
setConfirmPassword("")
|
||||
} else {
|
||||
const data = await res.json()
|
||||
|
||||
setToast({
|
||||
text: data.error ?? "Failed to update password",
|
||||
type: "error",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -74,24 +75,60 @@ const Password = () => {
|
|||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "var(--gap)",
|
||||
maxWidth: "300px",
|
||||
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%"} />
|
||||
<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%"} />
|
||||
<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%"} />
|
||||
<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>)
|
||||
<Button htmlType="submit" auto>
|
||||
Change Password
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default Password
|
|
@ -35,7 +35,7 @@ const Profile = () => {
|
|||
if (!name && !email && !bio) {
|
||||
setToast({
|
||||
text: "Please fill out at least one field",
|
||||
type: "error",
|
||||
type: "error"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -43,32 +43,33 @@ const Profile = () => {
|
|||
const data = {
|
||||
displayName: name,
|
||||
email,
|
||||
bio,
|
||||
bio
|
||||
}
|
||||
|
||||
const res = await fetch("/server-api/user/profile", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${Cookies.get("drift-token")}`,
|
||||
Authorization: `Bearer ${Cookies.get("drift-token")}`
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
setToast({
|
||||
text: "Profile updated",
|
||||
type: "success",
|
||||
type: "success"
|
||||
})
|
||||
} else {
|
||||
setToast({
|
||||
text: "Something went wrong updating your profile",
|
||||
type: "error",
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (<>
|
||||
return (
|
||||
<>
|
||||
<Note type="warning" marginBottom={"var(--gap)"}>
|
||||
This information will be publicly available on your profile
|
||||
</Note>
|
||||
|
@ -77,24 +78,48 @@ const Profile = () => {
|
|||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "var(--gap)",
|
||||
maxWidth: "300px",
|
||||
maxWidth: "300px"
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div>
|
||||
<label htmlFor="displayName">Display name</label>
|
||||
<Input id="displayName" width={"100%"} placeholder="my name" value={name || ''} onChange={handleNameChange} />
|
||||
<Input
|
||||
id="displayName"
|
||||
width={"100%"}
|
||||
placeholder="my name"
|
||||
value={name || ""}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<Input id="email" htmlType="email" width={"100%"} placeholder="my@email.io" value={email || ''} onChange={handleEmailChange} />
|
||||
<Input
|
||||
id="email"
|
||||
htmlType="email"
|
||||
width={"100%"}
|
||||
placeholder="my@email.io"
|
||||
value={email || ""}
|
||||
onChange={handleEmailChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="bio">Biography (max 250 characters)</label>
|
||||
<Textarea id="bio" width="100%" maxLength={250} placeholder="I enjoy..." value={bio || ''} onChange={handleBioChange} />
|
||||
<Textarea
|
||||
id="bio"
|
||||
width="100%"
|
||||
maxLength={250}
|
||||
placeholder="I enjoy..."
|
||||
value={bio || ""}
|
||||
onChange={handleBioChange}
|
||||
/>
|
||||
</div>
|
||||
<Button htmlType="submit" auto>Submit</Button>
|
||||
</form></>)
|
||||
<Button htmlType="submit" auto>
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Profile
|
|
@ -3,14 +3,16 @@ import PageSeo from "@components/page-seo"
|
|||
import HomeComponent from "@components/home"
|
||||
import { Page, Text } from "@geist-ui/core"
|
||||
import type { GetStaticProps } from "next"
|
||||
import { InferGetStaticPropsType } from 'next'
|
||||
type Props = {
|
||||
import { InferGetStaticPropsType } from "next"
|
||||
type Props =
|
||||
| {
|
||||
introContent: string
|
||||
introTitle: string
|
||||
rendered: string
|
||||
} | {
|
||||
}
|
||||
| {
|
||||
error: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async () => {
|
||||
try {
|
||||
|
@ -32,21 +34,26 @@ export const getStaticProps: GetStaticProps = async () => {
|
|||
// Next.js will attempt to re-generate the page:
|
||||
// - When a request comes in
|
||||
// - At most every 60 seconds
|
||||
revalidate: 60, // In seconds
|
||||
revalidate: 60 // In seconds
|
||||
}
|
||||
} catch (err) {
|
||||
// If there was an error, it's likely due to the server not running, so we attempt to regenerate the page
|
||||
return {
|
||||
props: {
|
||||
error: true,
|
||||
error: true
|
||||
},
|
||||
revalidate: 10, // In seconds
|
||||
revalidate: 10 // In seconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix props type
|
||||
const Home = ({ rendered, introContent, introTitle, error }: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
const Home = ({
|
||||
rendered,
|
||||
introContent,
|
||||
introTitle,
|
||||
error
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
return (
|
||||
<Page className={styles.wrapper}>
|
||||
<PageSeo />
|
||||
|
|
|
@ -69,7 +69,6 @@ export const getServerSideProps: GetServerSideProps = async ({
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
props: {
|
||||
post: json,
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
import { Button, Divider, Text, Fieldset, Input, Page, Note, Textarea } from "@geist-ui/core"
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Text,
|
||||
Fieldset,
|
||||
Input,
|
||||
Page,
|
||||
Note,
|
||||
Textarea
|
||||
} from "@geist-ui/core"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import styles from "@styles/Home.module.css"
|
||||
import SettingsPage from "@components/settings"
|
||||
|
@ -6,7 +15,10 @@ import SettingsPage from "@components/settings"
|
|||
const Settings = () => (
|
||||
<Page width={"100%"}>
|
||||
<PageSeo title="Drift - Settings" />
|
||||
<Page.Content className={styles.main} style={{ gap: 'var(--gap)', display: 'flex', flexDirection: 'column' }}>
|
||||
<Page.Content
|
||||
className={styles.main}
|
||||
style={{ gap: "var(--gap)", display: "flex", flexDirection: "column" }}
|
||||
>
|
||||
<SettingsPage />
|
||||
</Page.Content>
|
||||
</Page>
|
||||
|
|
|
@ -10,17 +10,17 @@ export const up: Migration = async ({ context: queryInterface }) =>
|
|||
}),
|
||||
queryInterface.addColumn("users", "displayName", {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
allowNull: true
|
||||
}),
|
||||
queryInterface.addColumn("users", "bio", {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
}),
|
||||
allowNull: true
|
||||
})
|
||||
])
|
||||
|
||||
export const down: Migration = async ({ context: queryInterface }) =>
|
||||
Promise.all([
|
||||
queryInterface.removeColumn("users", "email"),
|
||||
queryInterface.removeColumn("users", "displayName"),
|
||||
queryInterface.removeColumn("users", "bio"),
|
||||
queryInterface.removeColumn("users", "bio")
|
||||
])
|
||||
|
|
|
@ -195,7 +195,8 @@ auth.post("/signout", secretKey, async (req, res, next) => {
|
|||
}
|
||||
})
|
||||
|
||||
auth.put("/change-password",
|
||||
auth.put(
|
||||
"/change-password",
|
||||
jwt,
|
||||
celebrate({
|
||||
body: {
|
||||
|
|
|
@ -30,13 +30,14 @@ user.get("/self", jwt, async (req: UserJwtRequest, res, next) => {
|
|||
}
|
||||
})
|
||||
|
||||
user.put("/profile",
|
||||
user.put(
|
||||
"/profile",
|
||||
jwt,
|
||||
celebrate({
|
||||
body: {
|
||||
displayName: Joi.string().optional().allow(""),
|
||||
bio: Joi.string().optional().allow(""),
|
||||
email: Joi.string().optional().email().allow(""),
|
||||
email: Joi.string().optional().email().allow("")
|
||||
}
|
||||
}),
|
||||
async (req: UserJwtRequest, res, next) => {
|
||||
|
|
Loading…
Reference in a new issue