begin work on protected posts
This commit is contained in:
parent
65b0c8f7f3
commit
3f0212c5c6
14 changed files with 136 additions and 50 deletions
|
@ -2,9 +2,9 @@ import React from 'react'
|
|||
import MoonIcon from '@geist-ui/icons/moon'
|
||||
import SunIcon from '@geist-ui/icons/sun'
|
||||
import { Select } from '@geist-ui/core'
|
||||
import { ThemeProps } from '../../pages/_app'
|
||||
// import { useAllThemes, useTheme } from '@geist-ui/core'
|
||||
import styles from './header.module.css'
|
||||
import { ThemeProps } from '@lib/types'
|
||||
|
||||
const Controls = ({ changeTheme, theme }: ThemeProps) => {
|
||||
const switchThemes = (type: string | string[]) => {
|
||||
|
|
|
@ -1,29 +1,56 @@
|
|||
import { Button, ButtonDropdown, useToasts } from '@geist-ui/core'
|
||||
import { Button, ButtonDropdown, Input, Modal, Note, useModal, useToasts } from '@geist-ui/core'
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useState } from 'react'
|
||||
import generateUUID from '@lib/generate-uuid';
|
||||
import Document from '../document';
|
||||
import DocumentComponent from '../document';
|
||||
import FileDropzone from './drag-and-drop';
|
||||
import styles from './post.module.css'
|
||||
import Title from './title';
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
export type Document = {
|
||||
title: string
|
||||
content: string
|
||||
id: string
|
||||
}
|
||||
import type { PostVisibility, Document as DocumentType } from '@lib/types';
|
||||
import PasswordModal from './password';
|
||||
|
||||
const Post = () => {
|
||||
const { setToast } = useToasts()
|
||||
|
||||
const router = useRouter();
|
||||
const [title, setTitle] = useState<string>()
|
||||
const [docs, setDocs] = useState<Document[]>([{
|
||||
const [docs, setDocs] = useState<DocumentType[]>([{
|
||||
title: '',
|
||||
content: '',
|
||||
id: generateUUID()
|
||||
}])
|
||||
const [passwordModalVisible, setPasswordModalVisible] = useState(false)
|
||||
const sendRequest = useCallback(async (url: string, data: { visibility?: PostVisibility, title?: string, files?: DocumentType[], password?: string, userId: string }) => {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${Cookies.get('drift-token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
files: docs,
|
||||
...data,
|
||||
})
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const json = await res.json()
|
||||
router.push(`/post/${json.id}`)
|
||||
} else {
|
||||
const json = await res.json()
|
||||
setToast({
|
||||
text: json.message,
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
}, [docs, router, setToast, title])
|
||||
|
||||
const closePasswordModel = () => {
|
||||
setPasswordModalVisible(false)
|
||||
setSubmitting(false)
|
||||
}
|
||||
|
||||
const [isSubmitting, setSubmitting] = useState(false)
|
||||
|
||||
|
@ -31,29 +58,30 @@ const Post = () => {
|
|||
setDocs(docs.filter((doc) => doc.id !== id))
|
||||
}
|
||||
|
||||
const onSubmit = async (visibility: string) => {
|
||||
const onSubmit = async (visibility: PostVisibility, password?: string) => {
|
||||
setSubmitting(true)
|
||||
const response = await fetch('/server-api/posts/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${Cookies.get("drift-token")}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
files: docs,
|
||||
visibility,
|
||||
userId: Cookies.get("drift-userid"),
|
||||
})
|
||||
|
||||
if (visibility === 'protected' && !password) {
|
||||
setPasswordModalVisible(true)
|
||||
return
|
||||
}
|
||||
|
||||
await sendRequest('/server-api/posts/create', {
|
||||
title,
|
||||
files: docs,
|
||||
visibility,
|
||||
password,
|
||||
userId: Cookies.get('drift-userid') || ''
|
||||
})
|
||||
|
||||
const json = await response.json()
|
||||
|
||||
|
||||
setSubmitting(false)
|
||||
}
|
||||
|
||||
const onClosePasswordModal = () => {
|
||||
setPasswordModalVisible(false)
|
||||
setSubmitting(false)
|
||||
if (json.id)
|
||||
router.push(`/post/${json.id}`)
|
||||
else {
|
||||
setToast({ text: json.error.message, type: "error" })
|
||||
}
|
||||
}
|
||||
|
||||
const updateTitle = useCallback((title: string, id: string) => {
|
||||
|
@ -64,7 +92,7 @@ const Post = () => {
|
|||
setDocs(docs.map((doc) => doc.id === id ? { ...doc, content } : doc))
|
||||
}, [docs])
|
||||
|
||||
const uploadDocs = useCallback((files: Document[]) => {
|
||||
const uploadDocs = useCallback((files: DocumentType[]) => {
|
||||
// if no title is set and the only document is empty,
|
||||
const isFirstDocEmpty = docs.length === 1 && docs[0].title === '' && docs[0].content === ''
|
||||
const shouldSetTitle = !title && isFirstDocEmpty
|
||||
|
@ -87,7 +115,7 @@ const Post = () => {
|
|||
{
|
||||
docs.map(({ content, id, title }) => {
|
||||
return (
|
||||
<Document
|
||||
<DocumentComponent
|
||||
remove={() => remove(id)}
|
||||
key={id}
|
||||
editable={true}
|
||||
|
@ -120,10 +148,12 @@ const Post = () => {
|
|||
<ButtonDropdown.Item main onClick={() => onSubmit('private')}>Create Private</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit('public')} >Create Public</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit('unlisted')} >Create Unlisted</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit('protected')} >Create with Password</ButtonDropdown.Item>
|
||||
</ButtonDropdown>
|
||||
<PasswordModal isOpen={passwordModalVisible} onClose={onClosePasswordModal} onSubmit={(password) => onSubmit('protected', password)} />
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
export default Post
|
||||
export default Post
|
||||
|
|
50
client/components/new-post/password/index.tsx
Normal file
50
client/components/new-post/password/index.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { Input, Modal, Note, Spacer } from "@geist-ui/core"
|
||||
import { useState } from "react"
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSubmit: (password: string) => void
|
||||
}
|
||||
|
||||
const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify }: Props) => {
|
||||
const [password, setPassword] = useState<string>()
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
const onSubmit = () => {
|
||||
if (!password || !confirmPassword) {
|
||||
setError('Please enter a password')
|
||||
return
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError("Passwords do not match")
|
||||
return
|
||||
}
|
||||
|
||||
onSubmitAfterVerify(password)
|
||||
}
|
||||
|
||||
return (<>
|
||||
{<Modal visible={isOpen} >
|
||||
<Modal.Title>Enter a password</Modal.Title>
|
||||
<Modal.Content>
|
||||
{!error && <Note type="warning" label='Warning'>
|
||||
This doesn't protect your post from the server administrator.
|
||||
</Note>}
|
||||
{error && <Note type="error" label='Error'>
|
||||
{error}
|
||||
</Note>}
|
||||
<Spacer />
|
||||
<Input width={"100%"} label="Password" marginBottom={1} htmlType="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} />
|
||||
<Input width={"100%"} label="Confirm" htmlType="password" placeholder="Confirm Password" onChange={(e) => setConfirmPassword(e.target.value)} />
|
||||
</Modal.Content>
|
||||
<Modal.Action passive onClick={onClose}>Cancel</Modal.Action>
|
||||
<Modal.Action onClick={onSubmit}>Submit</Modal.Action>
|
||||
</Modal>}
|
||||
</>)
|
||||
}
|
||||
|
||||
|
||||
export default PasswordModal
|
|
@ -1,6 +1,5 @@
|
|||
import { Badge } from "@geist-ui/core"
|
||||
|
||||
type Visibility = "unlisted" | "private" | "public"
|
||||
import { Visibility } from "@lib/types"
|
||||
|
||||
type Props = {
|
||||
visibility: Visibility
|
||||
|
|
12
client/lib/types.d.ts
vendored
Normal file
12
client/lib/types.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
export type PostVisibility = "unlisted" | "private" | "public" | "protected"
|
||||
|
||||
export type ThemeProps = {
|
||||
theme: "light" | "dark" | string,
|
||||
changeTheme: () => void
|
||||
}
|
||||
|
||||
export type Document = {
|
||||
title: string
|
||||
content: string
|
||||
id: string
|
||||
}
|
|
@ -7,11 +7,7 @@ import useSharedState from '@lib/hooks/use-shared-state';
|
|||
import 'react-loading-skeleton/dist/skeleton.css'
|
||||
import { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import Head from 'next/head';
|
||||
|
||||
export type ThemeProps = {
|
||||
theme: "light" | "dark" | string,
|
||||
changeTheme: () => void
|
||||
}
|
||||
import { ThemeProps } from '@lib/types';
|
||||
|
||||
type AppProps<P = any> = {
|
||||
pageProps: P;
|
||||
|
|
|
@ -2,11 +2,11 @@ import styles from '@styles/Home.module.css'
|
|||
import { Page, Spacer, Text } from '@geist-ui/core'
|
||||
|
||||
import Header from '@components/header'
|
||||
import { ThemeProps } from './_app'
|
||||
import Document from '@components/document'
|
||||
import Image from 'next/image'
|
||||
import ShiftBy from '@components/shift-by'
|
||||
import PageSeo from '@components/page-seo'
|
||||
import { ThemeProps } from '@lib/types'
|
||||
|
||||
export function getStaticProps() {
|
||||
const introDoc = process.env.WELCOME_CONTENT
|
||||
|
|
|
@ -3,12 +3,10 @@ import NewPost from '@components/new-post'
|
|||
import { Page } from '@geist-ui/core'
|
||||
import useSignedIn from '@lib/hooks/use-signed-in'
|
||||
import Header from '@components/header'
|
||||
import { ThemeProps } from './_app'
|
||||
import { useRouter } from 'next/router'
|
||||
import PageSeo from '@components/page-seo'
|
||||
import { ThemeProps } from '@lib/types'
|
||||
|
||||
const New = ({ theme, changeTheme }: ThemeProps) => {
|
||||
const router = useRouter()
|
||||
const isSignedIn = useSignedIn()
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,10 +6,10 @@ import { useEffect, useState } from "react";
|
|||
import Document from '../../components/document'
|
||||
import Header from "../../components/header";
|
||||
import VisibilityBadge from "../../components/visibility-badge";
|
||||
import { ThemeProps } from "../_app";
|
||||
import PageSeo from "components/page-seo";
|
||||
import styles from './styles.module.css';
|
||||
import Cookies from "js-cookie";
|
||||
import { ThemeProps } from "@lib/types";
|
||||
|
||||
const Post = ({ theme, changeTheme }: ThemeProps) => {
|
||||
const [post, setPost] = useState<any>()
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Page } from "@geist-ui/core";
|
|||
import PageSeo from "@components/page-seo";
|
||||
import Auth from "@components/auth";
|
||||
import Header from "@components/header";
|
||||
import { ThemeProps } from "./_app";
|
||||
import { ThemeProps } from "@lib/types";
|
||||
|
||||
const SignIn = ({ theme, changeTheme }: ThemeProps) => (
|
||||
<Page width={"100%"}>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Page } from "@geist-ui/core";
|
|||
import Auth from "@components/auth";
|
||||
import Header from "@components/header";
|
||||
import PageSeo from '@components/page-seo';
|
||||
import { ThemeProps } from "./_app";
|
||||
import { ThemeProps } from "@lib/types";
|
||||
|
||||
const SignUp = ({ theme, changeTheme }: ThemeProps) => (
|
||||
<Page width="100%">
|
||||
|
|
|
@ -48,6 +48,9 @@ export class Post extends Model {
|
|||
@Column
|
||||
visibility!: string;
|
||||
|
||||
@Column
|
||||
password?: string;
|
||||
|
||||
@UpdatedAt
|
||||
@Column
|
||||
updatedAt!: Date;
|
||||
|
|
|
@ -26,7 +26,6 @@ posts.post('/create', jwt, async (req, res, next) => {
|
|||
throw new Error("Please provide a visibility.")
|
||||
}
|
||||
|
||||
// Create the "post" object
|
||||
const newPost = new Post({
|
||||
title: req.body.title,
|
||||
visibility: req.body.visibility,
|
||||
|
@ -35,7 +34,6 @@ posts.post('/create', jwt, async (req, res, next) => {
|
|||
await newPost.save()
|
||||
await newPost.$add('users', req.body.userId);
|
||||
const newFiles = await Promise.all(req.body.files.map(async (file) => {
|
||||
// Establish a "file" for each file in the request
|
||||
const newFile = new File({
|
||||
title: file.title,
|
||||
content: file.content,
|
||||
|
|
|
@ -4,7 +4,7 @@ import config from '../lib/config';
|
|||
import { sequelize } from '../lib/sequelize';
|
||||
|
||||
(async () => {
|
||||
await sequelize.sync();
|
||||
await sequelize.sync({ force: true });
|
||||
createServer(app)
|
||||
.listen(
|
||||
config.port,
|
||||
|
|
Loading…
Reference in a new issue