aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacky Zhao <[email protected]>2021-03-07 08:06:35 -0800
committerGitHub <[email protected]>2021-03-07 08:06:35 -0800
commit687cdafedd143a8b413a8ed0736e01471f0a3a62 (patch)
tree0f8700594afb386c1d0818e5a8495111cbd7dce8
parentMerge pull request #70 from jackyzha0/visual-overhaul (diff)
parentrefactor to use useFetchPaste hook (diff)
downloadctrl-v-687cdafedd143a8b413a8ed0736e01471f0a3a62.tar.xz
ctrl-v-687cdafedd143a8b413a8ed0736e01471f0a3a62.zip
Merge pull request #71 from jackyzha0/http-refactor
refactor to use useFetchPaste hook
-rw-r--r--frontend/src/App.js6
-rw-r--r--frontend/src/components/ViewPaste.js137
-rw-r--r--frontend/src/components/hooks/shared.js74
-rw-r--r--frontend/src/components/hooks/useFetchPaste.js66
-rw-r--r--frontend/src/components/modals/PasswordModal.js3
-rw-r--r--frontend/src/components/modals/shared.js5
-rw-r--r--frontend/src/components/pages/NewPaste.js (renamed from frontend/src/components/NewPaste.js)47
-rw-r--r--frontend/src/components/pages/Raw.js16
-rw-r--r--frontend/src/components/pages/ViewPaste.js65
-rw-r--r--frontend/src/components/renderers/Raw.js44
-rw-r--r--frontend/src/helpers/httpHelper.js68
11 files changed, 244 insertions, 287 deletions
diff --git a/frontend/src/App.js b/frontend/src/App.js
index a8da469..0a47baa 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -1,6 +1,6 @@
import React from 'react';
-import NewPaste from './components/NewPaste'
-import ViewPaste from './components/ViewPaste'
+import NewPaste from './components/pages/NewPaste'
+import ViewPaste from './components/pages/ViewPaste'
import styled from 'styled-components'
import {
BrowserRouter as Router,
@@ -8,7 +8,7 @@ import {
Route,
useParams
} from "react-router-dom";
-import Raw from './components/renderers/Raw'
+import Raw from './components/pages/Raw'
import ThemeProvider from './theme/ThemeProvider'
import GlobalStyle from './theme/GlobalStyle'
import {Watermark} from "./components/Watermark";
diff --git a/frontend/src/components/ViewPaste.js b/frontend/src/components/ViewPaste.js
deleted file mode 100644
index 9dd281c..0000000
--- a/frontend/src/components/ViewPaste.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import React, { useEffect, useState, useRef } from 'react';
-import Error from './Err';
-import { Text } from './Inputs';
-import CodeRenderer from './renderers/Code'
-import PasteInfo from './PasteInfo';
-import PasswordModal from './modals/PasswordModal'
-import { FetchPaste, FetchPasswordPaste } from '../helpers/httpHelper'
-import { LANGS } from './renderers/Code'
-import RenderDispatch from './renderers/RenderDispatch'
-
-function fmtDateStr(dateString) {
- const d = new Date(dateString)
- const options = { hour: '2-digit', minute: '2-digit', year: 'numeric', month: 'long', day: 'numeric' }
- return d.toLocaleDateString("en-US", options).toLocaleLowerCase()
-}
-
-const ViewPaste = (props) => {
- const [title, setTitle] = useState('fetching paste...');
- const [content, setContent] = useState('');
- const [hasPass, setHasPass] = useState(false);
- const [enteredPass, setEnteredPass] = useState('');
- const [validPass, setValidPass] = useState(false);
- const [expiry, setExpiry] = useState('');
- const [theme, setTheme] = useState('atom');
- const [isRenderMode, setIsRenderMode] = useState(false);
- const [language, setLanguage] = useState(LANGS.detect);
-
- useEffect(() => {
- setIsRenderMode(language === 'latex' || language === 'markdown')
- }, [language])
-
- const ErrorLabelRef = useRef(null);
-
- function validatePass(pass, onErrorCallBack) {
- FetchPasswordPaste(props.hash, pass)
- .then((response) => {
- setValidPass(true)
- setStateFromData(response.data)
- }).catch((error) => {
- const resp = error.response
-
- // 401 unauth (bad pass)
- if (resp.status === 401) {
- onErrorCallBack("incorrect pass")
- return
- }
-
- // otherwise, just log it lmao
- if (resp !== undefined) {
- const errTxt = `${resp.status}: ${resp.data}`
- onErrorCallBack(errTxt)
- } else {
- // some weird err (e.g. network)
- onErrorCallBack(error)
- }
- });
- }
-
- function setStateFromData(data) {
- document.title = data.title
- setTitle(data.title)
- setContent(data.content)
- setLanguage(data.language)
- setExpiry(fmtDateStr(data.expiry))
- }
-
- useEffect(() => {
- FetchPaste(props.hash)
- .then((response) => {
- setStateFromData(response.data)
- }).catch((error) => {
- const resp = error.response
-
- // network err
- if (!resp) {
- ErrorLabelRef.current.showMessage(error)
- return
- }
-
- // catch 401 unauth (password protected)
- if (resp.status === 401) {
- setHasPass(true)
- return
- }
-
- // some weird err
- if (resp !== undefined) {
- const errTxt = `${resp.status}: ${resp.data}`
- ErrorLabelRef.current.showMessage(errTxt, -1)
- return
- }
-
- // some weird err (e.g. network)
- ErrorLabelRef.current.showMessage(error, -1)
- })
- }, [props.hash])
-
- function getDisplay() {
- return isRenderMode ? <RenderDispatch
- language={language}
- content={content}
- /> : <CodeRenderer
- content={content}
- lang={language}
- theme={theme}
- id="pasteInput" />
- }
-
- return (
- <div>
- <PasswordModal
- hasPass={hasPass}
- validPass={validPass}
- value={enteredPass}
- onChange={(e) => setEnteredPass(e.target.value)}
- validateCallback={validatePass} />
- <Text
- label="title"
- value={title}
- id="titleInput"
- readOnly />
- {getDisplay()}
- <PasteInfo
- hash={props.hash}
- lang={language}
- theme={theme}
- expiry={expiry}
- toggleRenderCallback={() => setIsRenderMode(!isRenderMode)}
- isRenderMode={isRenderMode}
- onChange={(e) => setTheme(e.target.value)}
- err={<Error ref={ErrorLabelRef} />}
- />
- </div>
- );
-}
-
-export default ViewPaste \ No newline at end of file
diff --git a/frontend/src/components/hooks/shared.js b/frontend/src/components/hooks/shared.js
new file mode 100644
index 0000000..00d41e9
--- /dev/null
+++ b/frontend/src/components/hooks/shared.js
@@ -0,0 +1,74 @@
+import axios from 'axios';
+
+// uncomment for local dev
+// const base = `http://localhost:8080/api`
+const base = `https://api.ctrl-v.app/api`
+export function fetchPaste(hash, pass = "") {
+ const serverURL = `${base}/${hash}`
+
+ if (pass === "") {
+ return axios.get(serverURL)
+ } else {
+ const bodyFormData = new FormData();
+ bodyFormData.set('password', pass);
+ return axios({
+ method: 'post',
+ url: `${base}/${hash}`,
+ data: bodyFormData,
+ headers: { 'Content-Type': 'multipart/form-data' },
+ })
+ }
+}
+
+export function newPaste(paste) {
+ const {title, content, language, pass, expiry} = paste
+ const bodyFormData = new FormData();
+ bodyFormData.set('title', title);
+ bodyFormData.set('content', content);
+ bodyFormData.set('language', language);
+ bodyFormData.set('password', pass);
+ bodyFormData.set('expiry', parseExpiry(expiry));
+
+ return axios({
+ method: 'post',
+ url: base,
+ data: bodyFormData,
+ headers: { 'Content-Type': 'multipart/form-data' },
+ })
+}
+
+export function parseExpiry(e) {
+ var cur = new Date();
+ var inSeconds = 0
+ switch (e) {
+ case '5 years':
+ inSeconds = 600 * 6 * 24 * 7 * 4 * 12 * 5
+ break;
+ case '1 year':
+ inSeconds = 600 * 6 * 24 * 7 * 4 * 12
+ break;
+ case '1 month':
+ inSeconds = 600 * 6 * 24 * 7 * 4
+ break;
+ case '1 day':
+ inSeconds = 600 * 6 * 24
+ break;
+ case '1 hour':
+ inSeconds = 600 * 6
+ break;
+ case '10 min':
+ inSeconds = 600
+ break;
+ case '1 week':
+ default:
+ inSeconds = 600 * 6 * 24 * 7
+ break;
+ }
+ return new Date(cur.getTime() + inSeconds * 1000).toISOString();
+}
+
+export function fmtDateStr(dateString) {
+ const d = new Date(dateString)
+ const options = { hour: '2-digit', minute: '2-digit', year: 'numeric', month: 'long', day: 'numeric' }
+ return d.toLocaleDateString("en-US", options).toLocaleLowerCase()
+} \ No newline at end of file
diff --git a/frontend/src/components/hooks/useFetchPaste.js b/frontend/src/components/hooks/useFetchPaste.js
index a61ca1e..c394f5b 100644
--- a/frontend/src/components/hooks/useFetchPaste.js
+++ b/frontend/src/components/hooks/useFetchPaste.js
@@ -1,5 +1,67 @@
-import React from 'react'
+import {useEffect, useState} from 'react'
+import {fetchPaste, fmtDateStr} from './shared'
+import {LANGS} from "../renderers/Code";
-export default () => {
+export default (id) => {
+ const [loading, setLoading] = useState(true)
+ const [err, setErr] = useState()
+ const [requiresAuth, setRequiresAuth] = useState(false)
+ const [validPass, setValidPass] = useState(false)
+ const [result, setResult] = useState({
+ title: 'fetching paste...',
+ content: '',
+ language: LANGS.detect,
+ expiry: '',
+ })
+ const handleErr = error => {
+ const resp = error.response
+
+ // network err
+ if (!resp) {
+ setErr(error.toString())
+ return
+ }
+
+ // password protected
+ if (resp.status === 401) {
+ setRequiresAuth(true)
+ return
+ }
+
+ // catch all
+ const errTxt = `${resp.status}: ${resp.data}`
+ setErr(errTxt)
+ }
+
+ // callback to try verifying with password
+ const getWithPassword = (password, errorCallback) => {
+ fetchPaste(id, password)
+ .then(resp => {
+ setValidPass(true)
+ setStateFromData(resp.data)
+ })
+ .catch(e => errorCallback(e.response.data))
+ }
+
+
+ const setStateFromData = (data) => {
+ document.title = data.title
+ setResult({
+ title: data.title,
+ content: data.content,
+ language: data.language,
+ expiry: fmtDateStr(data.expiry)
+ })
+ }
+
+ // initial fetch
+ useEffect(() => {
+ fetchPaste(id)
+ .then(resp => setStateFromData(resp.data))
+ .catch(handleErr)
+ .finally(() => setLoading(false))
+ }, [id])
+
+ return { loading, err, requiresAuth, validPass, getWithPassword, result }
} \ No newline at end of file
diff --git a/frontend/src/components/modals/PasswordModal.js b/frontend/src/components/modals/PasswordModal.js
index a829091..5c4ab87 100644
--- a/frontend/src/components/modals/PasswordModal.js
+++ b/frontend/src/components/modals/PasswordModal.js
@@ -1,7 +1,7 @@
import React, { useRef } from 'react';
import Modal from 'react-modal';
import { Password } from '../Inputs'
-import {ModalHeader, Padding, modalStyles, Form} from './shared'
+import {ModalHeader, modalStyles, Form} from './shared'
import Error from '../Err';
import {SubmitButton} from "../Common/Button";
@@ -28,7 +28,6 @@ const PasswordModal = (props) => {
value={props.value}
onChange={props.onChange} />
<SubmitButton type="submit" value="continue" />
- <Padding />
<Error ref={ErrorLabel} />
</Form>
</Modal>
diff --git a/frontend/src/components/modals/shared.js b/frontend/src/components/modals/shared.js
index 0336818..4d5987a 100644
--- a/frontend/src/components/modals/shared.js
+++ b/frontend/src/components/modals/shared.js
@@ -18,9 +18,4 @@ export const Form = styled.form`
export const ModalHeader = styled.h3`
font-weight: 700
-`
-
-export const Padding = styled.span`
- content: '&nbsp;';
- margin-right: 2em;
` \ No newline at end of file
diff --git a/frontend/src/components/NewPaste.js b/frontend/src/components/pages/NewPaste.js
index b322351..19161da 100644
--- a/frontend/src/components/NewPaste.js
+++ b/frontend/src/components/pages/NewPaste.js
@@ -1,14 +1,14 @@
import React, { useEffect, useState, useRef } from 'react';
-import { Text, Code } from './Inputs'
-import OptionsContainer from './Options'
-import Error from './Err'
-import { PostNewPaste } from '../helpers/httpHelper'
-import PasteModal from './modals/PasteModal'
+import { Text, Code } from '../Inputs'
+import OptionsContainer from '../Options'
+import Error from '../Err'
+import PasteModal from '../modals/PasteModal'
import styled from 'styled-components'
-import CodeRenderer from './renderers/Code'
-import Latex from './renderers/Latex'
-import Markdown from './renderers/Markdown'
-import {Button, SubmitButton} from "./Common/Button";
+import CodeRenderer from '../renderers/Code'
+import Latex from '../renderers/Latex'
+import Markdown from '../renderers/Markdown'
+import {Button, SubmitButton} from "../Common/Button";
+import {newPaste} from "../hooks/shared";
const Flex = styled.div`
display: flex;
@@ -40,11 +40,7 @@ const NewPaste = () => {
const ErrorLabel = useRef(null);
useEffect(() => {
- if (title === "") {
- document.title = `ctrl-v`;
- } else {
- document.title = `ctrl-v | ${title}`;
- }
+ document.title = title === "" ? `ctrl-v` : `ctrl-v | ${title}`;
}, [title])
function handleSubmit(e) {
@@ -52,21 +48,20 @@ const NewPaste = () => {
// prevent resubmission
if (!hash) {
- PostNewPaste(title, content, language, pass, expiry)
- .then((response) => {
- // on success, redir
- setHash(response.data.hash)
- }).catch((error) => {
+ newPaste({title, content, language, pass, expiry})
+ .then(resp => {setHash(resp.data.hash)})
+ .catch((error) => {
const resp = error.response
-
- // some weird err
- if (resp !== undefined) {
- const errTxt = `${resp.status}: ${resp.data}`
- ErrorLabel.current.showMessage(errTxt)
- } else {
- // some weird err (e.g. network)
+
+ // some weird err (e.g. network)
+ if (!resp) {
ErrorLabel.current.showMessage(error)
+ return
}
+
+ // some weird err
+ const errTxt = `${resp.status}: ${resp.data}`
+ ErrorLabel.current.showMessage(errTxt)
});
}
}
diff --git a/frontend/src/components/pages/Raw.js b/frontend/src/components/pages/Raw.js
new file mode 100644
index 0000000..23ef6bf
--- /dev/null
+++ b/frontend/src/components/pages/Raw.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import styled from 'styled-components'
+import {CodeLike} from "../Common/mixins";
+import useFetchPaste from "../hooks/useFetchPaste";
+
+const RawText = styled.pre`
+ ${CodeLike}
+ padding: 0 1em;
+`
+
+const Raw = ({hash}) => {
+ const { err, result } = useFetchPaste(hash)
+ return <RawText>{result?.content || err}</RawText>
+}
+
+export default Raw \ No newline at end of file
diff --git a/frontend/src/components/pages/ViewPaste.js b/frontend/src/components/pages/ViewPaste.js
new file mode 100644
index 0000000..bc61314
--- /dev/null
+++ b/frontend/src/components/pages/ViewPaste.js
@@ -0,0 +1,65 @@
+import React, { useEffect, useState, useRef } from 'react';
+import Error from '../Err';
+import { Text } from '../Inputs';
+import CodeRenderer from '../renderers/Code'
+import PasteInfo from '../PasteInfo';
+import PasswordModal from '../modals/PasswordModal'
+import RenderDispatch from '../renderers/RenderDispatch'
+import useFetchPaste from "../hooks/useFetchPaste";
+
+const ViewPaste = (props) => {
+ const { err, requiresAuth, validPass, getWithPassword, result } = useFetchPaste(props.hash)
+ const {content, language, expiry, title} = result ?? {}
+ const [theme, setTheme] = useState('atom');
+ const [isRenderMode, setIsRenderMode] = useState(false);
+ const [enteredPass, setEnteredPass] = useState('');
+ const ErrorLabelRef = useRef(null);
+
+ if (err) {
+ ErrorLabelRef.current.showMessage(err, -1)
+ }
+
+ useEffect(() => {
+ setIsRenderMode(language === 'latex' || language === 'markdown')
+ }, [language])
+
+ function getDisplay() {
+ return isRenderMode ? <RenderDispatch
+ language={language}
+ content={content}
+ /> : <CodeRenderer
+ content={content}
+ lang={language}
+ theme={theme}
+ id="pasteInput" />
+ }
+
+ return (
+ <div>
+ <PasswordModal
+ hasPass={requiresAuth}
+ validPass={validPass}
+ value={enteredPass}
+ onChange={(e) => setEnteredPass(e.target.value)}
+ validateCallback={getWithPassword} />
+ <Text
+ label="title"
+ value={title}
+ id="titleInput"
+ readOnly />
+ {getDisplay()}
+ <PasteInfo
+ hash={props.hash}
+ lang={language}
+ theme={theme}
+ expiry={expiry}
+ toggleRenderCallback={() => setIsRenderMode(!isRenderMode)}
+ isRenderMode={isRenderMode}
+ onChange={(e) => setTheme(e.target.value)}
+ err={<Error ref={ErrorLabelRef} />}
+ />
+ </div>
+ );
+}
+
+export default ViewPaste \ No newline at end of file
diff --git a/frontend/src/components/renderers/Raw.js b/frontend/src/components/renderers/Raw.js
deleted file mode 100644
index 182bfff..0000000
--- a/frontend/src/components/renderers/Raw.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import styled from 'styled-components'
-import { FetchPaste } from '../../helpers/httpHelper'
-import {CodeLike} from "../Common/mixins";
-
-const RawText = styled.pre`
- ${CodeLike}
- padding: 0 1em;
-`
-
-const Raw = ({hash}) => {
- const [content, setContent] = useState('');
-
- useEffect(() => {
- FetchPaste(hash)
- .then((response) => {
- const data = response.data
- setContent(data.content)
- }).catch((error) => {
- const resp = error.response
-
- // catch 401 unauth (password protected)
- if (resp.status === 401) {
- setContent('err: password protected')
- return
- }
-
- // some weird err
- if (resp !== undefined) {
- const errTxt = `${resp.statusText}: ${resp.data}`
- setContent(errTxt)
- return
- }
-
- // some weird err (e.g. network)
- setContent(error)
- })}, [hash])
-
- return (
- <RawText>{content}</RawText>
- );
-}
-
-export default Raw \ No newline at end of file
diff --git a/frontend/src/helpers/httpHelper.js b/frontend/src/helpers/httpHelper.js
deleted file mode 100644
index 99b9513..0000000
--- a/frontend/src/helpers/httpHelper.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import axios from 'axios';
-
-// uncomment for local dev
-// const base = `http://localhost:8080/api`
-const base = `https://api.ctrl-v.app/api`
-
-export function FetchPaste(hash) {
- const serverURL = `${base}/${hash}`
- return axios.get(serverURL)
-}
-
-export function FetchPasswordPaste(hash, pass) {
- var bodyFormData = new FormData();
- bodyFormData.set('password', pass);
-
- return axios({
- method: 'post',
- url: `${base}/${hash}`,
- data: bodyFormData,
- headers: { 'Content-Type': 'multipart/form-data' },
- })
-}
-
-export function PostNewPaste(title, content, language, pass, expiry) {
- var bodyFormData = new FormData();
- bodyFormData.set('title', title);
- bodyFormData.set('content', content);
- bodyFormData.set('language', language);
- bodyFormData.set('password', pass);
- bodyFormData.set('expiry', parseExpiry(expiry));
-
- return axios({
- method: 'post',
- url: base,
- data: bodyFormData,
- headers: { 'Content-Type': 'multipart/form-data' },
- })
-}
-
-function parseExpiry(e) {
- var cur = new Date();
- var inSeconds = 0
- switch (e) {
- case '5 years':
- inSeconds = 600 * 6 * 24 * 7 * 4 * 12 * 5
- break;
- case '1 year':
- inSeconds = 600 * 6 * 24 * 7 * 4 * 12
- break;
- case '1 month':
- inSeconds = 600 * 6 * 24 * 7 * 4
- break;
- case '1 day':
- inSeconds = 600 * 6 * 24
- break;
- case '1 hour':
- inSeconds = 600 * 6
- break;
- case '10 min':
- inSeconds = 600
- break;
- case '1 week':
- default:
- inSeconds = 600 * 6 * 24 * 7
- break;
- }
- return new Date(cur.getTime() + inSeconds * 1000).toISOString();
-} \ No newline at end of file