From a7343e3b465ad9a79d455cf98739dd792cb60568 Mon Sep 17 00:00:00 2001 From: elysee15 <54010836+elysee15@users.noreply.github.com> Date: Thu, 5 Aug 2021 22:55:19 +0000 Subject: [PATCH] fix(create secret): create secret using backend endpoint (#3) --- src/components/CreateSecretForm.tsx | 68 +++----------------- src/pages/CreateSecret.tsx | 94 +++++++++++++++++++++++----- src/services/createSecret.ts | 7 +++ src/styles/createSecretFormStyles.ts | 20 ++++++ src/styles/createSecretStyles.ts | 24 +++++++ src/styles/index.ts | 3 + src/types/CreateSecretFormProps.ts | 8 +++ src/utils/interfaces/Lifetime.ts | 4 +- src/utils/interfaces/Secret.ts | 8 +++ src/utils/utils.ts | 10 +++ 10 files changed, 171 insertions(+), 75 deletions(-) create mode 100644 src/services/createSecret.ts create mode 100644 src/styles/createSecretFormStyles.ts create mode 100644 src/styles/createSecretStyles.ts create mode 100644 src/styles/index.ts create mode 100644 src/types/CreateSecretFormProps.ts create mode 100644 src/utils/interfaces/Secret.ts create mode 100644 src/utils/utils.ts diff --git a/src/components/CreateSecretForm.tsx b/src/components/CreateSecretForm.tsx index 3f173b2..5c20af5 100644 --- a/src/components/CreateSecretForm.tsx +++ b/src/components/CreateSecretForm.tsx @@ -1,30 +1,13 @@ -import { Button, makeStyles, Theme, Typography } from "@material-ui/core"; +import { Button, Typography } from "@material-ui/core"; import { TextField, Switch } from "formik-material-ui"; import { Field, Form, Formik } from "formik"; import { Autocomplete, AutocompleteRenderInputParams } from "formik-material-ui-lab"; import { TextField as MuiTextField } from "@material-ui/core"; -import * as Yup from "yup"; import { Lifetime } from "utils/interfaces"; import { useEffect } from "react"; - -const useStyles = makeStyles((theme: Theme) => ({ - form: { - display: "flex", - flexDirection: "column", - gap: theme.spacing(2), - maxWidth: 400, - margin: "auto", - "& .MuiTextField-root": { - width: "100%" - } - }, - height__full: { - height: "100%" - }, - hide: { - display: "none" - } -})); +import { CreateSecretFormProps } from "types/CreateSecretFormProps"; +import { preventNonNumericalInput } from "utils/utils"; +import { useStyles } from "styles/createSecretFormStyles"; const options: Lifetime[] = [ { value: "5m", label: "5 min" }, @@ -39,50 +22,17 @@ const options: Lifetime[] = [ { value: "168h", label: "7 days" } ]; -const initialValues = { - secret: "", - password: "", - accessType: true, - accessNumber: 1, - lifetime: { value: "7d", label: "7 days" } -}; - -const CreateSecretSchema = Yup.object().shape({ - secret: Yup.string().required("You must add a secret"), - password: Yup.string(), - lifetime: Yup.object().shape({ value: Yup.string(), label: Yup.string() }).nullable(), - accessType: Yup.boolean(), - accessNumber: Yup.number().max(108, "The max number is 108") -}); - -const CreateSecretForm: React.FC = () => { +const CreateSecretForm: React.FC = props => { const classes = useStyles(); - function preventNonNumericalInput(e: any) { - e = e || window.event; - const charCode = typeof e.which == "undefined" ? e.keyCode : e.which; - const charStr = String.fromCharCode(charCode); - - if (!charStr.match(/^[0-9]+$/)) e.preventDefault(); - } - return ( - { - setTimeout(() => { - setSubmitting(false); - alert(JSON.stringify(values, null, 2)); - }, 200); - }} - > + {({ isSubmitting, errors, values, setFieldValue }) => { useEffect(() => { if (values.accessType) { - setFieldValue("accessNumber", -1); + setFieldValue("accesses", -1); } else { - setFieldValue("accessNumber", 1); + setFieldValue("accesses", 1); } }, [values.accessType]); @@ -122,7 +72,7 @@ const CreateSecretForm: React.FC = () => { component={TextField} type="number" label="Number of access" - name="accessNumber" + name="accesses" size="small" variant="outlined" pattern="[0-9]*" diff --git a/src/pages/CreateSecret.tsx b/src/pages/CreateSecret.tsx index 840e617..24cbbf7 100644 --- a/src/pages/CreateSecret.tsx +++ b/src/pages/CreateSecret.tsx @@ -1,28 +1,92 @@ -import React from "react"; -import { Grid, makeStyles, Theme } from "@material-ui/core"; +import React, { useState } from "react"; +import { Grid } from "@material-ui/core"; import CreateSecretForm from "components/CreateSecretForm"; +import createSecret from "services/createSecret"; +import { Secret } from "utils/interfaces/Secret"; +import { FormikHelpers, FormikValues } from "formik"; +import { AxiosError, AxiosResponse } from "axios"; +import * as Yup from "yup"; +import { useStyles } from "styles/createSecretStyles"; +import { Alert, Color } from "@material-ui/lab"; +import { Lifetime } from "utils/interfaces"; -const useStyles = makeStyles((theme: Theme) => ({ - root: { - flexGrow: 1, - padding: theme.spacing(2) - }, - h__full: { - height: "100%" - }, - w__full: { - width: "100%" - } -})); +const CreateSecretSchema = Yup.object().shape({ + secret: Yup.string().required("You must add a secret"), + password: Yup.string(), + lifetime: Yup.object().shape({ value: Yup.string(), label: Yup.string() }).nullable(), + accessType: Yup.boolean(), + accesses: Yup.number().max(108, "The max number is 108") +}); + +interface Values { + secret: string; + password: string; + accessType: boolean; + accesses: number; + lifetime: Lifetime; +} + +const initialValues: Values = { + secret: "", + password: "", + accessType: true, + accesses: 1, + lifetime: { value: "168h", label: "7 days" } +}; const CreateSecret: React.FC = () => { + const [, setToken] = useState<{ token: string; expires: Date }>(); + const [message, setMessage] = useState<{ status?: Color; message?: string }>({ + status: undefined, + message: undefined + }); const classes = useStyles(); + function handleSubmit(values: FormikValues, helpers: FormikHelpers) { + const data: Secret = { + lifetime: values.lifetime.value, + secret: values.secret, + password: values.password, + accesses: values.accesses, + filename: values.filename || "", + is_base64: false + }; + + createSecret(data).then( + (response: AxiosResponse) => { + setToken(response.data); + + helpers.setSubmitting(false); + }, + (error: AxiosError) => { + console.error("[error when creating secret]", error.message); + setMessage({ status: "error", message: error.message }); + + setTimeout(() => { + setMessage({ status: undefined, message: undefined }); + }, 5000); + + helpers.setSubmitting(false); + } + ); + } + return (
- + + {message.message} + +
diff --git a/src/services/createSecret.ts b/src/services/createSecret.ts new file mode 100644 index 0000000..7dc49e2 --- /dev/null +++ b/src/services/createSecret.ts @@ -0,0 +1,7 @@ +import { AxiosPromise, AxiosRequestConfig } from "axios"; +import client from "config"; +import { Secret } from "utils/interfaces/Secret"; + +export default function createSecret(data: Secret, config: AxiosRequestConfig = {}): AxiosPromise { + return client.post("/secrets", data, config); +} diff --git a/src/styles/createSecretFormStyles.ts b/src/styles/createSecretFormStyles.ts new file mode 100644 index 0000000..bf54ffc --- /dev/null +++ b/src/styles/createSecretFormStyles.ts @@ -0,0 +1,20 @@ +import { makeStyles, Theme } from "@material-ui/core"; + +export const useStyles = makeStyles((theme: Theme) => ({ + form: { + display: "flex", + flexDirection: "column", + gap: theme.spacing(2), + maxWidth: 400, + margin: "auto", + "& .MuiTextField-root": { + width: "100%" + } + }, + height__full: { + height: "100%" + }, + hide: { + display: "none" + } +})); diff --git a/src/styles/createSecretStyles.ts b/src/styles/createSecretStyles.ts new file mode 100644 index 0000000..1dca50a --- /dev/null +++ b/src/styles/createSecretStyles.ts @@ -0,0 +1,24 @@ +import { makeStyles, Theme } from "@material-ui/core"; + +export const useStyles = makeStyles((theme: Theme) => ({ + root: { + flexGrow: 1, + padding: theme.spacing(2) + }, + h__full: { + height: "100%" + }, + w__full: { + width: "100%", + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)" + }, + alert: { + maxWidth: 400, + margin: "0 auto", + boxSizing: "border-box", + marginBottom: "1rem" + } +})); diff --git a/src/styles/index.ts b/src/styles/index.ts new file mode 100644 index 0000000..5a226ff --- /dev/null +++ b/src/styles/index.ts @@ -0,0 +1,3 @@ +export * as createSecretStyles from "./createSecretStyles"; +export * as footerStyles from "./footerStyles"; +export * as createSecretFormStyles from "./createSecretFormStyles"; diff --git a/src/types/CreateSecretFormProps.ts b/src/types/CreateSecretFormProps.ts new file mode 100644 index 0000000..ff09265 --- /dev/null +++ b/src/types/CreateSecretFormProps.ts @@ -0,0 +1,8 @@ +import { FormikHelpers, FormikValues } from "formik"; +import { ObjectSchema } from "yup"; + +export type CreateSecretFormProps = { + onSubmit: (values: FormikValues, helpers: FormikHelpers) => void; + validationSchema: ObjectSchema; + initialValues: FormikValues; +}; diff --git a/src/utils/interfaces/Lifetime.ts b/src/utils/interfaces/Lifetime.ts index 30ed00d..64cf053 100644 --- a/src/utils/interfaces/Lifetime.ts +++ b/src/utils/interfaces/Lifetime.ts @@ -1,4 +1,6 @@ +export type LifetimeValue = "5m" | "15m" | "30m" | "1h" | "2h" | "3h" | "24h" | "48h" | "72h" | "168h"; + export interface Lifetime { label: string; - value: string; + value: LifetimeValue; } diff --git a/src/utils/interfaces/Secret.ts b/src/utils/interfaces/Secret.ts new file mode 100644 index 0000000..bf59050 --- /dev/null +++ b/src/utils/interfaces/Secret.ts @@ -0,0 +1,8 @@ +export interface Secret { + secret: string; + password: string; + accesses: number; + lifetime: string; + filename?: string; + is_base64: false; +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..11e0293 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,10 @@ +import { Form } from "formik"; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function preventNonNumericalInput(e: React.KeyboardEvent) { + e = e || window.event; + const charCode = typeof e.which == "undefined" ? e.keyCode : e.which; + const charStr = String.fromCharCode(charCode); + + if (!charStr.match(/^[0-9]+$/)) e.preventDefault(); +}