From 173c03730a45e3a89840cf00fdc0fdd89cdc479f Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Wed, 29 Jun 2022 21:51:49 +0100 Subject: [PATCH] fix form issues --- Accounts/src/App.tsx | 8 +- Accounts/src/Pages/Register.tsx | 61 +++--- Accounts/src/Utils/ApiPaths.ts | 5 + Accounts/webpack.config.js | 2 +- Common/Models/BaseModel.ts | 6 + Common/Models/EmailVerificationToken.ts | 3 + Common/Models/Probe.ts | 3 + Common/Models/Project.ts | 3 + Common/Models/User.ts | 3 + Common/Types/API/HTTPResponse.ts | 30 ++- Common/Types/API/Route.ts | 4 +- Common/Types/API/URL.ts | 12 ++ Common/Types/Database/CrudApiEndpoint.ts | 7 + Common/Utils/API.ts | 2 +- CommonServer/Utils/StartServer.ts | 6 +- CommonUI/src/Components/Button/Button.tsx | 5 +- CommonUI/src/Components/Forms/BasicForm.tsx | 178 +++++++++--------- .../src/Components/Forms/BasicModelForm.tsx | 2 + CommonUI/src/Components/Forms/ModelForm.tsx | 67 +++++-- CommonUI/src/Components/Loader/Loader.tsx | 7 + CommonUI/src/Config.ts | 4 +- CommonUI/src/Utils/API/{BaseAPI.ts => API.ts} | 9 +- CommonUI/src/Utils/API/DashboardAPI.ts | 2 +- CommonUI/src/Utils/API/IdentityAPI.ts | 2 +- CommonUI/src/Utils/API/IntegrationAPI.ts | 2 +- CommonUI/src/Utils/BrandColors.ts | 1 + CommonUI/src/Utils/User.ts | 16 +- Dashboard/src/App.tsx | 7 +- Dashboard/webpack.config.js | 2 +- Identity/API/AuthenticationAPI.ts | 2 +- docker-compose.dev.yml | 1 - 31 files changed, 302 insertions(+), 160 deletions(-) create mode 100644 Accounts/src/Utils/ApiPaths.ts create mode 100644 Common/Types/Database/CrudApiEndpoint.ts rename CommonUI/src/Utils/API/{BaseAPI.ts => API.ts} (85%) diff --git a/Accounts/src/App.tsx b/Accounts/src/App.tsx index 6d220096eb..73b44d3d78 100644 --- a/Accounts/src/App.tsx +++ b/Accounts/src/App.tsx @@ -4,14 +4,20 @@ import LoginPage from './Pages/Login'; import SsoLoginPage from './Pages/SsoLogin'; import ForgotPasswordPage from './Pages/ForgotPassword'; import RegisterPage from './Pages/Register'; - +import { DASHBOARD_URL } from 'CommonUI/src/Config'; import 'CommonUI/src/Styles/theme.scss'; import Navigation from 'CommonUI/src/Utils/Navigation'; import VerifyEmail from './Pages/VerifyEmail'; +import User from 'CommonUI/src/Utils/User'; function App(): ReactElement { Navigation.setNavigateHook(useNavigate()); Navigation.setLocation(useLocation()); + + if (User.isLoggedIn()) { + Navigation.navigate(DASHBOARD_URL); + } + return (
diff --git a/Accounts/src/Pages/Register.tsx b/Accounts/src/Pages/Register.tsx index af4e7408b7..2168ec2b7f 100644 --- a/Accounts/src/Pages/Register.tsx +++ b/Accounts/src/Pages/Register.tsx @@ -1,28 +1,24 @@ -import React, { FunctionComponent, useState } from 'react'; -import BasicModelForm from 'CommonUI/src/Components/Forms/BasicModelForm'; +import React, { FunctionComponent } from 'react'; +import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; import User from 'Common/Models/User'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import IdentityAPI from 'CommonUI/src/Utils/API/IdentityAPI'; import Link from 'CommonUI/src/Components/Link/Link'; import Route from 'Common/Types/API/Route'; -import { JSONObject } from 'Common/Types/JSON'; import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimePNG/7.png'; +import { DASHBOARD_URL } from 'CommonUI/src/Config'; +import { JSONObject } from 'Common/Types/JSON'; +import UserUtil from "CommonUI/src/Utils/User"; +import Navigation from 'CommonUI/src/Utils/Navigation'; +import Email from 'Common/Types/Email'; +import ObjectID from 'Common/Types/ObjectID'; +import Name from 'Common/Types/Name'; +import URL from 'Common/Types/API/URL'; +import { SIGNUP_API_URL } from "../Utils/ApiPaths"; const RegisterPage: FunctionComponent = () => { - const [isLaoding, setIsLoading] = useState(false); const user: User = new User(); - - const submitForm: Function = async (values: FormValues) => { - setIsLoading(true); - - await IdentityAPI.post(new Route('/signup'), { - user: values as JSONObject, - }); - - setIsLoading(false); - }; + const apiUrl: URL = SIGNUP_API_URL; return (
@@ -55,12 +51,21 @@ const RegisterPage: FunctionComponent = () => {

- + model={user} - isLoading={isLaoding} id="register-form" showAsColumns={2} maxPrimaryButtonWidth={true} + initialValues= { + { + email: '', + name: '', + companyName: '', + companyPhoneNumber: '', + password: '', + confirmPassword: '' + } + } fields={[ { field: { @@ -89,7 +94,7 @@ const RegisterPage: FunctionComponent = () => { }, fieldType: FormFieldSchemaType.Text, - placeholder: 'Company Name', + placeholder: 'Acme, Inc.', required: true, title: 'Company Name', }, @@ -101,7 +106,7 @@ const RegisterPage: FunctionComponent = () => { fieldType: FormFieldSchemaType.Text, required: true, - placeholder: 'Phone Number', + placeholder: '+1-123-456-7890', title: 'Phone Number', }, { @@ -136,8 +141,22 @@ const RegisterPage: FunctionComponent = () => { required: true, }, ]} - onSubmit={submitForm} + apiUrl={apiUrl} + formType={FormType.Create} submitButtonText={'Sign Up'} + onSuccess={(value: JSONObject) => { + const user: User = User.fromJSON(value["user"] as JSONObject, User) as User; + const token: string = value["token"] as string; + + UserUtil.setAccessToken(token); + UserUtil.setEmail(user.email as Email); + UserUtil.setUserId(user.id as ObjectID); + UserUtil.setName(user.name as Name); + + // go to dashboard, user should be logged in. + Navigation.navigate(DASHBOARD_URL); + + }} />
diff --git a/Accounts/src/Utils/ApiPaths.ts b/Accounts/src/Utils/ApiPaths.ts new file mode 100644 index 0000000000..d57d414a17 --- /dev/null +++ b/Accounts/src/Utils/ApiPaths.ts @@ -0,0 +1,5 @@ +import Route from 'Common/Types/API/Route'; +import URL from 'Common/Types/API/URL'; +import { IDENTITY_URL } from 'CommonUI/src/Config'; + +export const SIGNUP_API_URL: URL = IDENTITY_URL.addRoute(new Route("/signup")); \ No newline at end of file diff --git a/Accounts/webpack.config.js b/Accounts/webpack.config.js index 97b96c6cfd..026cf54bbe 100644 --- a/Accounts/webpack.config.js +++ b/Accounts/webpack.config.js @@ -63,5 +63,5 @@ module.exports = { writeToDisk: true, }, }, - devtool: 'inline-source-map', + devtool: 'eval-source-map', } \ No newline at end of file diff --git a/Common/Models/BaseModel.ts b/Common/Models/BaseModel.ts index 5d3388ea18..9957c96744 100644 --- a/Common/Models/BaseModel.ts +++ b/Common/Models/BaseModel.ts @@ -27,6 +27,7 @@ import HashedString from '../Types/HashedString'; import Email from '../Types/Email'; import Phone from '../Types/Phone'; import PositiveNumber from '../Types/PositiveNumber'; +import Route from '../Types/API/Route'; export type DbTypes = | string @@ -100,6 +101,7 @@ export default class BaseModel extends BaseEntity { public slugifyColumn!: string | null; public saveSlugToColumn!: string | null; + public crudApiPath!: Route | null; // If this resource is by projectId, which column does projectId belong to? public projectIdColumn!: string | null; @@ -621,6 +623,10 @@ export default class BaseModel extends BaseEntity { return this.slugifyColumn; } + public getCrudApiPath(): Route | null { + return this.crudApiPath; + } + public getSaveSlugToColumn(): string | null { return this.saveSlugToColumn; } diff --git a/Common/Models/EmailVerificationToken.ts b/Common/Models/EmailVerificationToken.ts index 532c50222d..62569cf609 100755 --- a/Common/Models/EmailVerificationToken.ts +++ b/Common/Models/EmailVerificationToken.ts @@ -6,7 +6,10 @@ import User from './User'; import ColumnLength from '../Types/Database/ColumnLength'; import Email from '../Types/Email'; import TableColumn from '../Types/Database/TableColumn'; +import CrudApiEndpoint from '../Types/Database/CrudApiEndpoint'; +import Route from '../Types/API/Route'; +@CrudApiEndpoint(new Route("/email-verification-token")) @Entity({ name: 'EmailVerificationToken', }) diff --git a/Common/Models/Probe.ts b/Common/Models/Probe.ts index 740fed8f7f..26a9a6f68c 100755 --- a/Common/Models/Probe.ts +++ b/Common/Models/Probe.ts @@ -9,7 +9,10 @@ import SlugifyColumn from '../Types/Database/SlugifyColumn'; import URL from '../Types/API/URL'; import User from './User'; import TableColumn from '../Types/Database/TableColumn'; +import CrudApiEndpoint from '../Types/Database/CrudApiEndpoint'; +import Route from '../Types/API/Route'; +@CrudApiEndpoint(new Route("/probe")) @SlugifyColumn('name', 'slug') @Entity({ name: 'Probe', diff --git a/Common/Models/Project.ts b/Common/Models/Project.ts index e0ac600a9c..074e1edd5e 100644 --- a/Common/Models/Project.ts +++ b/Common/Models/Project.ts @@ -6,7 +6,10 @@ import PositiveNumber from '../Types/PositiveNumber'; import ObjectID from '../Types/ObjectID'; import ColumnLength from '../Types/Database/ColumnLength'; import TableColumn from '../Types/Database/TableColumn'; +import CrudApiEndpoint from '../Types/Database/CrudApiEndpoint'; +import Route from '../Types/API/Route'; +@CrudApiEndpoint(new Route("/project")) @Entity({ name: 'Project', }) diff --git a/Common/Models/User.ts b/Common/Models/User.ts index 2885164e8d..8dc3334fd7 100644 --- a/Common/Models/User.ts +++ b/Common/Models/User.ts @@ -14,7 +14,10 @@ import HashedString from '../Types/HashedString'; import PublicRecordPermissions from '../Types/Database/AccessControls/Public/PublicRecordPermissions'; import TableColumn from '../Types/Database/TableColumn'; import PublicColumnPermissions from '../Types/Database/AccessControls/Public/PublicColumnPermissions'; +import CrudApiEndpoint from '../Types/Database/CrudApiEndpoint'; +import Route from '../Types/API/Route'; +@CrudApiEndpoint(new Route("/user")) @PublicRecordPermissions({ create: true, readAsList: false, diff --git a/Common/Types/API/HTTPResponse.ts b/Common/Types/API/HTTPResponse.ts index 2ab986cc16..4922bc002d 100644 --- a/Common/Types/API/HTTPResponse.ts +++ b/Common/Types/API/HTTPResponse.ts @@ -31,14 +31,30 @@ export default class HTTPResponse< public constructor(statusCode: number, data: JSONObjectOrArray) { this.statusCode = statusCode; this.jsonData = data; + this.data = data as T; + } - let obj!: T; + public isSuccess(): boolean { + return this.statusCode === 200; + } - if (obj instanceof BaseModel) { - // this.data = BaseModel.fromJSON(data) as T; - this.data = data as T; - } else { - this.data = data as T; - } + public isFailure(): boolean { + return this.statusCode !== 200; + } + + public isNotAuthorized(): boolean { + return this.statusCode === 401; + } + + public isTooManyRequests(): boolean { + return this.statusCode === 429; + } + + public isPaymentDeclined(): boolean { + return this.statusCode === 402; + } + + public isServerError(): boolean { + return this.statusCode === 500; } } diff --git a/Common/Types/API/Route.ts b/Common/Types/API/Route.ts index bf9b4d25c8..d310a70eea 100644 --- a/Common/Types/API/Route.ts +++ b/Common/Types/API/Route.ts @@ -24,8 +24,8 @@ export default class Route { if (this.route.endsWith('/') && routeToBeAdded.trim().startsWith('/')) { routeToBeAdded = routeToBeAdded.trim().substring(1); // remove leading "/" from route } - const newRoute: Route = new Route(this.route + routeToBeAdded); - return newRoute; + this.route = new Route(this.route + routeToBeAdded).route; + return this; } public toString(): string { diff --git a/Common/Types/API/URL.ts b/Common/Types/API/URL.ts index 8fb4fe6c59..8a191662d0 100644 --- a/Common/Types/API/URL.ts +++ b/Common/Types/API/URL.ts @@ -103,6 +103,16 @@ export default class URL extends DatabaseProperty { return new URL(protocol, hostname, route); } + public addRoute(route: Route | string): URL { + if (typeof route === "string") { + this.route.addRoute(new Route(route)); + } else { + this.route.addRoute(route); + } + + return this; + } + protected static override toDatabase( value: URL | FindOperator ): string | null { @@ -113,6 +123,8 @@ export default class URL extends DatabaseProperty { return null; } + + protected static override fromDatabase(_value: string): URL | null { if (_value) { return URL.fromString(_value); diff --git a/Common/Types/Database/CrudApiEndpoint.ts b/Common/Types/Database/CrudApiEndpoint.ts new file mode 100644 index 0000000000..e8191aa7fb --- /dev/null +++ b/Common/Types/Database/CrudApiEndpoint.ts @@ -0,0 +1,7 @@ +import Route from "../API/Route"; + +export default (apiPath: Route) => { + return (ctr: Function) => { + ctr.prototype.CrudApiPath = apiPath; + }; +}; diff --git a/Common/Utils/API.ts b/Common/Utils/API.ts index 0e1f448b54..3a4d18316b 100644 --- a/Common/Utils/API.ts +++ b/Common/Utils/API.ts @@ -192,7 +192,7 @@ export default class API { return await this.fetch(HTTPMethod.POST, url, data, headers); } - private static async fetch< + public static async fetch< T extends JSONObject | JSONArray | BaseModel | Array >( method: HTTPMethod, diff --git a/CommonServer/Utils/StartServer.ts b/CommonServer/Utils/StartServer.ts index f0583003c5..042f32b63d 100644 --- a/CommonServer/Utils/StartServer.ts +++ b/CommonServer/Utils/StartServer.ts @@ -79,17 +79,21 @@ app.use( res: ExpressResponse, next: NextFunction ) => { + logger.error("Code"); logger.error(err); if (res.headersSent) { return next(err); } + logger.error("Code"); + logger.error((err as Exception).code); if (err instanceof Exception) { + logger.error("Exception Error") res.status((err as Exception).code); res.send({ error: (err as Exception).message }); } else { - res.status(500); + res.status(400); res.send({ error: err }); } } diff --git a/CommonUI/src/Components/Button/Button.tsx b/CommonUI/src/Components/Button/Button.tsx index 2f88798f9b..62a1a8ed92 100644 --- a/CommonUI/src/Components/Button/Button.tsx +++ b/CommonUI/src/Components/Button/Button.tsx @@ -4,7 +4,8 @@ import ShortcutKey from '../ShortcutKey/ShortcutKey'; import ButtonType from './ButtonTypes'; import CSS from 'csstype'; import Icon, { IconProp, SizeProp, ThickProp } from '../Icon/Icon'; - +import Loader, { LoaderType } from '../Loader/Loader'; +import { White } from '../../Utils/BrandColors'; export enum ButtonStyleType { PRIMARY, SECONDRY, @@ -164,7 +165,7 @@ const Button: FunctionComponent = ({
)} - {isLoading &&
Implement Loader here
} + {isLoading &&
} ); }; diff --git a/CommonUI/src/Components/Forms/BasicForm.tsx b/CommonUI/src/Components/Forms/BasicForm.tsx index 7dc87ef6e5..b32d2320de 100644 --- a/CommonUI/src/Components/Forms/BasicForm.tsx +++ b/CommonUI/src/Components/Forms/BasicForm.tsx @@ -10,6 +10,7 @@ import { JSONObject } from 'Common/Types/JSON'; import FormFieldSchemaType from './Types/FormFieldSchemaType'; import Email from 'Common/Types/Email'; import Link from '../Link/Link'; +import Alert, { AlertType } from '../Alerts/Alert'; export const DefaultValidateFunction: Function = ( _values: FormValues @@ -32,6 +33,7 @@ export interface ComponentProps { onCancel?: (() => void) | null; cancelButtonText?: string | null; maxPrimaryButtonWidth?: boolean; + error: string | null } function getFieldType(fieldType: FormFieldSchemaType): string { @@ -50,9 +52,12 @@ function getFieldType(fieldType: FormFieldSchemaType): string { const BasicForm: Function = ( props: ComponentProps ): ReactElement => { + + const getFormField: Function = ( field: DataField, - index: number + index: number, + isDisabled: boolean ): ReactElement => { const fieldType: string = field.fieldType ? getFieldType(field.fieldType) @@ -65,10 +70,14 @@ const BasicForm: Function = ( if (props.showAsColumns && props.showAsColumns > 2) { throw new BadDataException( 'showAsCOlumns should be <= 2. It is currently ' + - props.showAsColumns + props.showAsColumns ); } + const fieldName: string = field.overideFieldKey + ? field.overideFieldKey + : (Object.keys(field.field)[0] as string); + return (