Commit 0252fead authored by David Haynes's avatar David Haynes 🙆

Merge branch '180-new-golink' into 'go-three'

Resolve "3.0 /new form"

Closes #180

See merge request !132
parents 32312625 4e8fff95
Pipeline #3947 passed with stage
in 1 minute and 29 seconds
import React from "react";
import { Input, FormFeedback } from "reactstrap";
/**
* https://codebrains.io/build-react-forms-validation-formik-reactstrap/
*/
const MasonstrappedFormInput = ({
field,
form: { touched, errors },
...props
}) => (
<div>
<Input
invalid={!!(touched[field.name] && errors[field.name])}
{...field}
{...props}
/>
{touched[field.name] && errors[field.name] && (
<FormFeedback>{errors[field.name]}</FormFeedback>
)}
</div>
);
export default MasonstrappedFormInput;
import * as Yup from "yup";
import moment from "moment";
const NewGoLinkValidator = Yup.object().shape({
targetURL: Yup.string()
.required("You must submit a target URL!")
.url("Not a valid URL!")
.max(1000, "URL is too long!"),
shortcode: Yup.string()
.required("You must submit a shortcode!")
.max(20, "Your shortcode is too long!"),
expires: Yup.date()
.nullable()
.min(
moment(new Date())
.add(1, "days")
.format(),
"You cannot expire your Go link on that day."
)
});
export default NewGoLinkValidator;
......@@ -34,8 +34,8 @@ const NavBar = props => {
<Collapse isOpen={!collapsed} navbar>
<Nav className="mx-auto" navbar>
<NavItem>
<NavLink href="#/dhaynes" active={pathname == "/dhaynes"}>
Dhaynes
<NavLink href="#/new" active={pathname == "/new"}>
New Link
</NavLink>
</NavItem>
<NavItem>
......
import React, { useState, useEffect } from "react";
import NewGoLinkValidator from "../Molecules/NewGoLinkValidator";
import { Formik, Field, Form } from "formik";
import GetCSRFToken from "../../Utils/GetCSRFToken";
import { SingleDatePicker } from "react-dates";
import ParseAPIErrors from "../../Utils/ParseAPIErrors";
import moment from "moment";
import {
FormGroup,
Button,
Label,
FormText,
Row,
Col,
InputGroup,
InputGroupAddon
} from "reactstrap";
import MasonstrappedFormInput from "../Molecules/MasonstrappedFormInput";
const divStyle = {
display: "block"
};
const style2 = {
borderTopLeftRadius: "0rem",
borderBottomLeftRadius: "0rem"
};
const NewGoLinkForm = props => {
const [focused, setFocused] = useState(false);
const [expiresFieldDisabled, setexpiresFieldDisabled] = useState(true);
var today = new Date();
var tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
const toggleExpiresField = () => {
setexpiresFieldDisabled(!expiresFieldDisabled);
};
return (
<Formik
// Init our form with some blank values
initialValues={{
shortcode: "",
targetURL: "",
expires: moment(tomorrow) // You must set a default date to start with
}}
// Yup client side validation
validationSchema={NewGoLinkValidator}
// Handle form submission
onSubmit={(
{ targetURL, shortcode, expires },
{ setSubmitting, setErrors }
) => {
if (expiresFieldDisabled) {
expires = null;
} else {
expires = expires.format();
}
const APISubmission = {
destination: targetURL,
short: shortcode,
date_expires: expires
};
fetch("/api/golinks/", {
method: "post",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": GetCSRFToken()
},
body: JSON.stringify(APISubmission)
}).then(response => {
response.json().then(body => {
if (!response.ok) {
console.log(body);
const parsedAPIErrors = ParseAPIErrors(body);
setErrors(parsedAPIErrors);
} else {
props.history.push("/debug");
}
});
});
setSubmitting(false);
}}
// Render out our form
render={({ values, isSubmitting, setFieldValue, errors }) => (
<Form>
<Row>
<Col md="12">
<FormGroup>
<Label for="targetURL">Target URL</Label>
<Field
name="targetURL"
type="text"
placeholder="https://longwebsitelink.com"
component={MasonstrappedFormInput}
/>
<FormText>The URL that you would like to shorten.</FormText>
</FormGroup>
</Col>
</Row>
<Row>
<Col>
<FormGroup>
<Label for="shortcode">Shortcode</Label>
<InputGroup>
<InputGroupAddon addonType="prepend" style={divStyle}>
https://go.gmu.edu/
</InputGroupAddon>
<Field
name="shortcode"
type="text"
placeholder=""
className="form-control"
style={style2}
component={MasonstrappedFormInput}
/>
</InputGroup>
<FormText>The unique address for your target URL.</FormText>
</FormGroup>
</Col>
</Row>
<legend />
<Row>
<Col>
<h4 className="font-weight-light">
(Optional) Expire your Go link.
</h4>
</Col>
</Row>
<Row>
<Col>
<p className="text-muted">
A Go link may be set to expire on a specific date. When that
happens, anyone who visits the Go link will not be redirected to
the target URL and the shortcode will be freed up for other
users to use. You cannot un-expire a Go link.
</p>
</Col>
</Row>
<Row>
<Col>
<div className="custom-control custom-checkbox">
<input
type="checkbox"
className="custom-control-input"
id="customCheck1"
onChange={toggleExpiresField}
/>
<label className="custom-control-label" htmlFor="customCheck1">
Expire my Go link.
</label>
</div>
</Col>
</Row>
<Row>
<Col>
<FormGroup>
<Label className="mt-3" htmlFor="expires">
Date of Expiration
</Label>{" "}
<br />
<SingleDatePicker
date={values["expires"]} // momentPropTypes.momentObj or null
onDateChange={date => setFieldValue("expires", date)} // PropTypes.func.isRequired
focused={focused} // PropTypes.bool
onFocusChange={({ focused }) => setFocused(focused)} // PropTypes.func.isRequired
id="expires" // PropTypes.string.isRequired,
disabled={expiresFieldDisabled}
readOnly={true}
showDefaultInputIcon={true}
numberOfMonths={1}
/>
<FormText>
You cannot expire Go links on the same day (or before) they
are created.
</FormText>
{errors.expires ? <div>{errors.expires}</div> : null}
</FormGroup>
</Col>
</Row>
<legend />
<Row>
<Col>
<h4 className="font-weight-light">
(Optional) Force GMU login before redirect.
</h4>
</Col>
</Row>
<Row>
<Col>
<p className="text-muted">
A Go link that forces login requires any user to first log in
before being redirected to the target URL. This may be used to
protect target URLs from non GMU users.
</p>
</Col>
</Row>
<Row>
<Col>
<div className="custom-control custom-checkbox">
<input
type="checkbox"
className="custom-control-input"
disabled={true}
/>
<label className="custom-control-label">
Require GMU login.
</label>
</div>
</Col>
</Row>
<legend />
<Row>
<Col>
<h4 className="font-weight-light">
(Optional) Self destruct your Go link.
</h4>
</Col>
</Row>
<Row>
<Col>
<p className="text-muted">
A Go link that is set to self destruct will delete itself after
a number of clicks. For example if a Go link is set to self
destruct after 1 click, the moment after the first person vists
the link, the Go link is deleted. This can be used as an
alternative to date expiration if you know exactly how many
times Go should process redirects for your Go link.
</p>
</Col>
</Row>
<Row>
<Col>
<div className="custom-control custom-checkbox">
<input
type="checkbox"
className="custom-control-input"
disabled={true}
/>
<label className="custom-control-label">
Self destruct my Go link.
</label>
</div>
</Col>
</Row>
<legend />
<Row>
<Col md="4">
<Button
type="submit"
disabled={isSubmitting}
outline
block
color="primary"
>
Submit
</Button>
</Col>
</Row>
</Form>
)}
/>
);
};
export default NewGoLinkForm;
import NavBar from "./NavBar";
import NewGoLinkForm from "./NewGoLinkForm";
export { NavBar };
export { NavBar, NewGoLinkForm };
import React from "react";
import { PageTemplate } from "Components";
const DhaynesPage = () => (
<PageTemplate>
<h2>DAVID HAYNES</h2>
</PageTemplate>
);
export default DhaynesPage;
import React from "react";
import AuthedPageTemplate from "../Templates/AuthedPageTemplate";
import NewGolinkForm from "../Organisms/NewGoLinkForm";
import { Row, Col } from "reactstrap";
const NewGoLinkPage = props => {
return (
<AuthedPageTemplate {...props}>
<Row>
<Col>
<h2 className="mt-4 font-weight-light">Create a new Go link</h2>
</Col>
</Row>
<Row>
<Col>
<p className="text-muted">
A Go link is composed of the original "target" URL, and the unique
"shortcode" to be used in the Go link.
</p>
<legend />
</Col>
</Row>
<NewGolinkForm {...props} />
</AuthedPageTemplate>
);
};
export default NewGoLinkPage;
import HomePage from "./HomePage";
import AboutPage from "./AboutPage";
import DhaynesPage from "./DhaynesPage";
import DebugCRUD from "./DebugCRUD";
import NewGoLinkPage from "./NewGoLinkPage";
export { HomePage, AboutPage, DhaynesPage, DebugCRUD };
export { HomePage, AboutPage, DebugCRUD, NewGoLinkPage };
......@@ -5,8 +5,8 @@ import {
DebugDelete,
DebugUpdate
} from "./Molecules";
import { NavBar } from "./Organisms";
import { HomePage, AboutPage, DhaynesPage, DebugCRUD } from "./Pages";
import { NavBar, NewGoLinkForm } from "./Organisms";
import { HomePage, AboutPage, DebugCRUD, NewGoLinkPage } from "./Pages";
import { PageTemplate, AuthedPageTemplate } from "./Templates";
export {
......@@ -18,11 +18,12 @@ export {
DebugUpdate,
//Organisms
NavBar,
NewGoLinkForm,
//Pages
HomePage,
AboutPage,
DhaynesPage,
DebugCRUD,
NewGoLinkPage,
//Templates
PageTemplate,
AuthedPageTemplate
......
/**
*
* const APISubmission = {
destination: targetURL,
short: shortcode,
date_expires: expires
};
* Bind API errors on field submissions to local Formik fields.
* @param {object} apiResponse
*/
const ParseAPIErrors = apiResponse => {
const parsedAPIErrors = {};
if (apiResponse.short) {
if (apiResponse.short.length > 0) {
parsedAPIErrors.shortcode = apiResponse.short[0];
} else {
parsedAPIErrors.shortcode = apiResponse.short;
}
}
if (apiResponse.destination) {
parsedAPIErrors.targetURL = apiResponse.targetURL;
}
if (apiResponse.date_expires) {
parsedAPIErrors.expires = apiResponse.date_expires;
}
return parsedAPIErrors;
};
export default ParseAPIErrors;
......@@ -3,9 +3,9 @@ import { Route, withRouter, Switch } from "react-router-dom";
import { NavBar } from "Components";
const Home = lazy(() => import("../Components/Pages/HomePage"));
const Dhaynes = lazy(() => import("../Components/Pages/DhaynesPage"));
const About = lazy(() => import("../Components/Pages/AboutPage"));
const DebugCRUD = lazy(() => import("../Components/Pages/DebugCRUD"));
const NewGoLinkPage = lazy(() => import("../Components/Pages/NewGoLinkPage"));
const NavBarWithRouter = withRouter(props => <NavBar {...props} />);
......@@ -15,9 +15,9 @@ const Routes = () => (
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/dhaynes" component={Dhaynes} />
<Route path="/about" component={About} />
<Route path="/debug" component={DebugCRUD} />
<Route path="/new" component={NewGoLinkPage} />
<Route render={() => <div>404</div>} />
</Switch>
</Suspense>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment