From 28c84efcd5f3fba656fd6739d5d9cc9f9e74b695 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson <93477693+innocuous-symmetry@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:05:57 -0600 Subject: [PATCH] user registration connects to front end --- client/src/components/pages/Login.tsx | 11 +-- .../pages/Register/register.aboutyou.tsx | 30 +++++--- client/src/components/ui/Form.tsx | 25 ++----- client/src/hooks/useNow.tsx | 3 + client/src/util/apiUtils.ts | 9 ++- server/auth/index.ts | 27 ++++---- server/db/examplevals.ts | 15 +++- server/db/seed.ts | 9 +-- server/models/user.ts | 18 +++-- server/routes/auth.ts | 2 + server/schemas/index.ts | 69 ++++++++++--------- server/util/now.ts | 2 + 12 files changed, 130 insertions(+), 90 deletions(-) create mode 100644 client/src/hooks/useNow.tsx create mode 100644 server/util/now.ts diff --git a/client/src/components/pages/Login.tsx b/client/src/components/pages/Login.tsx index c63cb0a..95c4e12 100644 --- a/client/src/components/pages/Login.tsx +++ b/client/src/components/pages/Login.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { IUserAuth } from "../../schemas"; import { attemptLogin } from "../../util/apiUtils"; import { Button, Page, Panel } from "../ui"; @@ -11,6 +11,10 @@ export default function Login() { password: '' }) + const getFormState = useCallback((received: IUserAuth) => { + setInput(received); + }, []) + const handleLogin = async () => { if (!input.email || !input.password) return; const result = await attemptLogin(input); @@ -23,9 +27,7 @@ export default function Login() { labels: ["Email", "Password"], dataTypes: Object.keys(input), initialState: input, - setState: setInput, - submitButtonText: "Log In", - submitFunction: handleLogin + getState: getFormState } useEffect(() => { @@ -38,6 +40,7 @@ export default function Login() { { form } + diff --git a/client/src/components/pages/Register/register.aboutyou.tsx b/client/src/components/pages/Register/register.aboutyou.tsx index b6c741f..c397c1e 100644 --- a/client/src/components/pages/Register/register.aboutyou.tsx +++ b/client/src/components/pages/Register/register.aboutyou.tsx @@ -1,8 +1,8 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { v4 } from "uuid"; import { IUser } from "../../../schemas"; import { attemptRegister } from "../../../util/apiUtils"; -import { Page, Panel } from "../../ui"; +import { Button, Page, Panel } from "../../ui"; import Divider from "../../ui/Divider"; import Form, { FormConfig } from "../../ui/Form"; @@ -17,21 +17,34 @@ export default function AboutYou() { active: true }); + const getFormState = useCallback((received: IUser) => { + setInput(received); + }, []); + const formConfig: FormConfig = { parent: "register", - keys: Object.keys(input), + keys: ['firstname', 'lastname', 'handle', 'email', 'password'], initialState: input, - labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password", "Active?"], - dataTypes: ['text', 'text', 'text', 'email', 'password', 'text'], - setState: setInput, - submitButtonText: 'Register', - submitFunction: () => console.log(input) + labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password"], + dataTypes: ['text', 'text', 'text', 'email', 'password'], + getState: getFormState } useEffect(() => { setForm(new Form(formConfig).mount()); }, []) + const handleRegister = async () => { + for (let key of Object.keys(input)) { + if (!input[key as keyof IUser]) return; + } + + console.log(input); + + const result = await attemptRegister(input); + console.log(result); + } + return (

Hi! Thanks for being here.

@@ -42,6 +55,7 @@ export default function AboutYou() { { form } +
) diff --git a/client/src/components/ui/Form.tsx b/client/src/components/ui/Form.tsx index a24db07..f88b3e4 100644 --- a/client/src/components/ui/Form.tsx +++ b/client/src/components/ui/Form.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, Dispatch, SetStateAction, useState } from "react"; +import { ChangeEvent } from "react"; import { v4 } from 'uuid'; /** @@ -12,11 +12,9 @@ export interface FormConfig { parent: string keys: string[] initialState: T - setState: Dispatch> + getState: (received: T) => void labels?: string[] dataTypes?: string[] - submitFunction?: (params: any) => any - submitButtonText?: string } export default class Form{ @@ -26,20 +24,16 @@ export default class Form{ public dataTypes: any[] public length: number; public state: T; - public submitButtonText?: string; - public submitFunction?: (params: any) => any; - public setState: any + public getState: (received: T) => void constructor(config: FormConfig){ this.parent = config.parent; this.keys = config.keys; this.labels = config.labels || this.keys; this.length = config.keys.length; - this.submitFunction = config.submitFunction || undefined; - this.submitButtonText = config.submitButtonText || undefined; this.dataTypes = config.dataTypes || new Array(this.keys.length).fill('text'); this.state = config.initialState; - this.setState = config.setState; + this.getState = config.getState; } update(e: ChangeEvent, idx: number) { @@ -49,10 +43,7 @@ export default class Form{ } this.state = newState; - this.setState(newState); - - console.log(this.state); - this.mount(); + this.getState(newState); } mount() { @@ -72,12 +63,6 @@ export default class Form{ ) } - if (this.submitFunction) { - output.push( - - ) - } - return output; } } \ No newline at end of file diff --git a/client/src/hooks/useNow.tsx b/client/src/hooks/useNow.tsx new file mode 100644 index 0000000..87ceb86 --- /dev/null +++ b/client/src/hooks/useNow.tsx @@ -0,0 +1,3 @@ +export const useNow = () => { + return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now()); +} \ No newline at end of file diff --git a/client/src/util/apiUtils.ts b/client/src/util/apiUtils.ts index 06da29b..e8ed47a 100644 --- a/client/src/util/apiUtils.ts +++ b/client/src/util/apiUtils.ts @@ -15,8 +15,13 @@ export const attemptLogin = async (data: IUserAuth) => { } export const attemptRegister = async (data: IUser) => { - const result = await fetch(API + 'auth/register/', { method: "POST" }) - .then(response => response.json()); + const result = await fetch(API + 'auth/register/', { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(data) + }).then(response => response.json()); return result; } diff --git a/server/auth/index.ts b/server/auth/index.ts index b596a62..d8c86ff 100644 --- a/server/auth/index.ts +++ b/server/auth/index.ts @@ -2,36 +2,37 @@ import { IUser, IUserAuth } from "../schemas"; import { User } from "../models/user"; import createError from "http-errors"; import bcrypt from "bcrypt"; -import { QueryResult } from "pg"; +import now from "../util/now"; const UserInstance = new User(); export default class AuthService { // methods for local strategies - async register(data: IUser): Promise> { + async register(data: IUser) { const { email, password } = data; + + data.datecreated = now; + data.datemodified = now; + data.active = true; + try { const user = await UserInstance.getOneByEmail(email); if (user) throw createError('409', 'Email already in use'); - - let createdUser: IUser | null = null; - bcrypt.genSalt((err, salt) => { + bcrypt.genSalt(10, (err, salt) => { if (err) throw err; - bcrypt.hash(password, salt, (err, hash) => { + bcrypt.hash(password, salt, async (err, hash) => { if (err) throw err; - - createdUser = { + const newData = { ...data, password: hash } + + const result = await UserInstance.post(newData); + if (result) console.log(result); + return result; }) }) - - if (!createdUser) throw createError('400', 'Error creating user'); - const result = await UserInstance.post(createdUser); - if (!result) throw createError('400', 'Error creating user'); - return result; } catch (e: any) { throw new Error(e); } diff --git a/server/db/examplevals.ts b/server/db/examplevals.ts index 5370bbf..8f10da6 100644 --- a/server/db/examplevals.ts +++ b/server/db/examplevals.ts @@ -6,7 +6,7 @@ export default async function populate() { const populateUsers = ` INSERT INTO recipin.appusers - (firstname, lastname, handle, email, password, active, dateregistered, datelastactive) + (firstname, lastname, handle, email, password, active, datecreated, datemodified) VALUES ('Mikayla', 'Dobson', 'innocuoussymmetry', 'mikaylaherself@gmail.com', 'password1', true, $1, $1), ('Emily', 'Dobson', 'emjdobson', 'emily@email.com', 'password2', true, $1, $1), @@ -68,9 +68,20 @@ export default async function populate() { ; ` + const populateComments = ` + INSERT INTO recipin.cmp_recipecomments + (commentbody, datecreated, recipeid, authorid) + VALUES + ('Very cool recipe!', $1, 2, 2), + ('I love making this one', $1, 1, 2), + ('Thanks for sharing this', $1, 1, 4) + ; + ` + const allStatements: Array = [ populateUsers, populateRecipes, populateCollection, - populateIngredients, populateGroceryList, populateFriendships + populateIngredients, populateGroceryList, populateFriendships, + populateComments ]; await pool.query(setup); diff --git a/server/db/seed.ts b/server/db/seed.ts index c2dd2fb..4f3f81d 100644 --- a/server/db/seed.ts +++ b/server/db/seed.ts @@ -21,8 +21,8 @@ dotenv.config(); email varchar NOT NULL UNIQUE, password varchar NOT NULL, active boolean NOT NULL, - dateregistered varchar NOT NULL, - datelastactive varchar NOT NULL + datecreated varchar NOT NULL, + datemodified varchar NOT NULL ); ` @@ -76,7 +76,8 @@ dotenv.config(); CREATE TABLE IF NOT EXISTS recipin.cmp_recipecomments ( id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, commentbody varchar NOT NULL, - parentcommentid int, + datecreated varchar NOT NULL, + recipeid int REFERENCES recipin.recipe (id) NOT NULL, authorid int REFERENCES recipin.appusers (id) NOT NULL ); ` @@ -112,7 +113,7 @@ dotenv.config(); ` const allStatements = [ - setRole, appusers, ingredient, collection, recipe, + setRole, appusers, ingredient, collection, recipe, recipecomments, groceryList, recipeingredient, userscollections, userfriendships ] diff --git a/server/models/user.ts b/server/models/user.ts index 066335f..c834e9e 100644 --- a/server/models/user.ts +++ b/server/models/user.ts @@ -1,6 +1,7 @@ import { IUser } from "../schemas"; import pgPromise from "pg-promise"; import pool from '../db'; +import now from "../util/now"; const pgp = pgPromise({ capSQL: true }); export class User { @@ -67,13 +68,22 @@ export class User { } - async post(data: IUser): Promise | null> { + async post(data: IUser) { const { firstname, lastname, handle, email, password, active } = data; + const datecreated = now; + const datemodified = now; + try { - const statement = `INSERT INTO recipin.appusers (firstname, lastname, handle, email, password, active) VALUES ($1, $2, $3, $4, $5, $6)`; - const params = [firstname, lastname, handle, email, password, active]; + const statement = ` + INSERT INTO recipin.appusers ( + firstname, lastname, handle, email, password, + active, datecreated, datemodified) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *; + `; + const params = [firstname, lastname, handle, email, password, active, datecreated, datemodified]; const result = await pool.query(statement, params); - if (result.rows.length) return result.rows as Array; + if (result.rows.length) return result.rows; return null; } catch (error: any) { throw new Error(error); diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 881fb8b..056989a 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -23,6 +23,8 @@ export const authRoute = (app: Express, passport: PassportStatic) => { router.post('/register', async (req, res, next) => { try { const data: IUser = req.body; + const now = new Intl.DateTimeFormat('en-US', {}) + const response = await AuthInstance.register(data); res.status(200).send(response); } catch(e) { diff --git a/server/schemas/index.ts b/server/schemas/index.ts index f4575ec..bced4cb 100644 --- a/server/schemas/index.ts +++ b/server/schemas/index.ts @@ -1,45 +1,48 @@ -export interface IUser { +interface HasHistory { + datecreated?: string + datemodified?: string +} + +interface CanDeactivate { + active?: boolean +} + +interface DBEntity { id?: number +} + +export interface IUser extends DBEntity, HasHistory, CanDeactivate { firstname: string lastname: string handle: string email: string password: string - active: boolean -} - -export interface IRecipe { - id?: number - name: string - description?: string - preptime: string - removed: boolean - authoruserid?: IUser["id"] -} - -export interface IIngredient { - id?: number - name: string - description?: string -} - -export interface ICollection { - id?: number - name: string - active: string - ismaincollection: boolean - ownerid?: IUser["id"] -} - -export interface IGroceryList { - id?: number - name: string - recipes?: IRecipe["id"][] - active: boolean - ownerid?: IUser["id"] } export interface IUserAuth { email: string password: string +} + +export interface IRecipe extends DBEntity, HasHistory, CanDeactivate { + name: string + description?: string + preptime: string + authoruserid?: IUser["id"] +} + +export interface IIngredient extends DBEntity, HasHistory { + name: string + description?: string +} + +export interface ICollection extends DBEntity, HasHistory, CanDeactivate { + name: string + ismaincollection: boolean + ownerid?: IUser["id"] +} + +export interface IGroceryList extends DBEntity, HasHistory, CanDeactivate { + name: string + ownerid?: IUser["id"] } \ No newline at end of file diff --git a/server/util/now.ts b/server/util/now.ts new file mode 100644 index 0000000..bd37cfb --- /dev/null +++ b/server/util/now.ts @@ -0,0 +1,2 @@ +const now = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now()) +export default now; \ No newline at end of file