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