defined a form class

This commit is contained in:
Mikayla Dobson
2022-11-21 13:39:17 -06:00
parent 57a16e429e
commit 48f6a60e77
10 changed files with 195 additions and 26 deletions

View File

@@ -11,11 +11,13 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.4.3", "react-router-dom": "^6.4.3",
"sass": "^1.56.1" "sass": "^1.56.1",
"uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.24", "@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8", "@types/react-dom": "^18.0.8",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^2.2.0", "@vitejs/plugin-react": "^2.2.0",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"vite": "^3.2.3" "vite": "^3.2.3"
@@ -555,6 +557,12 @@
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"dev": true "dev": true
}, },
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz",
@@ -1629,6 +1637,14 @@
"browserslist": ">= 4.21.0" "browserslist": ">= 4.21.0"
} }
}, },
"node_modules/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz",
@@ -2071,6 +2087,12 @@
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"dev": true "dev": true
}, },
"@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"@vitejs/plugin-react": { "@vitejs/plugin-react": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz",
@@ -2713,6 +2735,11 @@
"picocolors": "^1.0.0" "picocolors": "^1.0.0"
} }
}, },
"uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
},
"vite": { "vite": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz",

View File

@@ -12,11 +12,13 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.4.3", "react-router-dom": "^6.4.3",
"sass": "^1.56.1" "sass": "^1.56.1",
"uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.24", "@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8", "@types/react-dom": "^18.0.8",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^2.2.0", "@vitejs/plugin-react": "^2.2.0",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"vite": "^3.2.3" "vite": "^3.2.3"

View File

@@ -1,8 +1,11 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import { IUserAuth } from "../../schemas"; import { IUserAuth } from "../../schemas";
import { attemptLogin } from "../../util/apiUtils";
import { Button, Page, Panel } from "../ui"; import { Button, Page, Panel } from "../ui";
import Form, { FormConfig } from "../ui/Form";
export default function Login() { export default function Login() {
const [form, setForm] = useState<JSX.Element[]>();
const [input, setInput] = useState<IUserAuth>({ const [input, setInput] = useState<IUserAuth>({
email: '', email: '',
password: '' password: ''
@@ -10,18 +13,31 @@ export default function Login() {
const handleLogin = async () => { const handleLogin = async () => {
if (!input.email || !input.password) return; if (!input.email || !input.password) return;
const result = await attemptLogin(input);
console.log(result);
} }
const formConfig: FormConfig<IUserAuth> = {
parent: 'login',
keys: Object.keys(input),
labels: ["Email", "Password"],
dataTypes: Object.keys(input),
initialState: input,
setState: setInput,
submitButtonText: "Log In",
submitFunction: handleLogin
}
useEffect(() => {
setForm(new Form<IUserAuth>(formConfig).mount())
}, [])
return ( return (
<Page> <Page>
<h1>Hello! Nice to see you again.</h1> <h1>Hello! Nice to see you again.</h1>
<Panel extraStyles="form-panel"> <Panel extraStyles="form-panel">
<label htmlFor="login-email">Email</label> { form }
<input type="text" id="login-email" onChange={(e) => setInput({...input, email: e.target.value})}></input>
<label htmlFor="login-password">Password</label>
<input type="text" id="login-password" onChange={(e) => setInput({...input, password: e.target.value})}></input>
<Button onClick={() => {}}>Log In</Button>
</Panel> </Panel>
<aside>Not registered yet? You can do that <a href="/register">here.</a></aside> <aside>Not registered yet? You can do that <a href="/register">here.</a></aside>

View File

@@ -1,15 +1,48 @@
import { Page } from "../../ui"; import { useEffect, useMemo, useState } from "react";
import { v4 } from "uuid";
import { IUser } from "../../../schemas";
import { attemptRegister } from "../../../util/apiUtils";
import { Page, Panel } from "../../ui";
import Divider from "../../ui/Divider";
import Form, { FormConfig } from "../../ui/Form";
export default function AboutYou() { export default function AboutYou() {
const [form, setForm] = useState<JSX.Element[]>([<p key={v4()}>Loading content...</p>]);
const [input, setInput] = useState<IUser>({
firstname: '',
lastname: '',
handle: '',
email: '',
password: '',
active: true
});
const formConfig: FormConfig<IUser> = {
parent: "register",
keys: Object.keys(input),
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)
}
useEffect(() => {
setForm(new Form<IUser>(formConfig).mount());
}, [])
return ( return (
<Page> <Page>
<h1>Hi! Thanks for being here.</h1> <h1>Hi! Thanks for being here.</h1>
{/* divider */} <Divider />
<h2>Tell us a bit about yourself:</h2> <h2>Tell us a bit about yourself:</h2>
{/* auth form */} <Panel extraStyles="form-panel two-columns">
{ form }
</Panel>
</Page> </Page>
) )
} }

View File

@@ -0,0 +1,83 @@
import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
import { v4 } from 'uuid';
/**
* For the generation of more complex form objects with
* larger stateful values; expects to receive an object of
* type T to a form which can mutate T with a state setter
* of type Dispatch<SetStateAction<T>>
**/
export interface FormConfig<T> {
parent: string
keys: string[]
initialState: T
setState: Dispatch<SetStateAction<T>>
labels?: string[]
dataTypes?: string[]
submitFunction?: (params: any) => any
submitButtonText?: string
}
export default class Form<T>{
public parent: string;
public labels: string[];
public keys: string[];
public dataTypes: any[]
public length: number;
public state: T;
public submitButtonText?: string;
public submitFunction?: (params: any) => any;
public setState: any
constructor(config: FormConfig<T>){
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;
}
update(e: ChangeEvent<HTMLElement>, idx: number) {
let newState = {
...this.state,
[this.keys[idx]]: e.target['value' as keyof EventTarget]
}
this.state = newState;
this.setState(newState);
console.log(this.state);
this.mount();
}
mount() {
let output = new Array<JSX.Element>();
for (let i = 0; i < this.length; i++) {
output.push(
<div id={`${this.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${this.parent}-${this.keys[i]}`}>{this.labels[i]}</label>
<input
type={this.dataTypes[i]}
id={`${this.parent}-${this.keys[i]}`}
onChange={(e) => this.update(e, i)}
value={this.state[i as keyof T] as string}>
</input>
</div>
)
}
if (this.submitFunction) {
output.push(
<button key={v4()} onClick={this.submitFunction}>{this.submitButtonText || 'Button'}</button>
)
}
return output;
}
}

View File

@@ -11,7 +11,7 @@ const Navbar = () => {
const navbarLoggedIn = ( const navbarLoggedIn = (
<div id="navbar"> <div id="navbar">
<div className="navbar-block"> <div className="navbar-block">
<h1>RECIPIN</h1> <a href="/">RECIPIN</a>
</div> </div>
<div className="navbar-block"> <div className="navbar-block">
<p>Hi, {user}</p> <p>Hi, {user}</p>
@@ -24,7 +24,7 @@ const Navbar = () => {
const navbarNotLoggedIn = ( const navbarNotLoggedIn = (
<div id="navbar"> <div id="navbar">
<div className="navbar-block"> <div className="navbar-block">
<h1>RECIPIN</h1> <a href="/">RECIPIN</a>
</div> </div>
<div className='navbar-block'> <div className='navbar-block'>
<button>LOG IN</button> <button>LOG IN</button>
@@ -35,7 +35,7 @@ const Navbar = () => {
const navbarRegistering = ( const navbarRegistering = (
<div id="navbar"> <div id="navbar">
<div className="navbar-block"> <div className="navbar-block">
<h1>RECIPIN</h1> <a href="/">RECIPIN</a>
</div> </div>
<div className="navbar-block"> <div className="navbar-block">
<p>Hi, {user}</p> <p>Hi, {user}</p>

View File

@@ -2,6 +2,8 @@
@import "../helpers/placeholders"; @import "../helpers/placeholders";
.Panel { .Panel {
display: flex;
flex-direction: column;
background-color: $papyrus; background-color: $papyrus;
color: black; color: black;
margin: 1rem 0; margin: 1rem 0;

View File

@@ -1,10 +1,15 @@
import { IUser } from "../schemas"; import { IUser, IUserAuth } from "../schemas";
const API = import.meta.env.APISTRING || "http://localhost:8080/"; const API = import.meta.env.APISTRING || "http://localhost:8080/";
// auth handlers // auth handlers
export const attemptLogin = async (email: string, password: string) => { export const attemptLogin = async (data: IUserAuth) => {
const result = await fetch(API + 'auth/login/', { method: "POST" }) const result: Array<keyof IUser> | null = await fetch(API + 'auth/login/', {
.then(response => response.json()); method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(response => response.json());
return result; return result;
} }

View File

@@ -12,6 +12,7 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
router.post('/login', passport.authenticate('local'), async (req, res, next) => { router.post('/login', passport.authenticate('local'), async (req, res, next) => {
try { try {
const data: IUserAuth = req.body; const data: IUserAuth = req.body;
console.log(data);
const response = await AuthInstance.login(data); const response = await AuthInstance.login(data);
res.status(200).send(response); res.status(200).send(response);
} catch(e) { } catch(e) {

View File

@@ -1,5 +1,5 @@
export interface IUser { export interface IUser {
id: number id?: number
firstname: string firstname: string
lastname: string lastname: string
handle: string handle: string
@@ -9,34 +9,34 @@ export interface IUser {
} }
export interface IRecipe { export interface IRecipe {
id: number id?: number
name: string name: string
description?: string description?: string
preptime: string preptime: string
removed: boolean removed: boolean
authoruserid: IUser["id"] authoruserid?: IUser["id"]
} }
export interface IIngredient { export interface IIngredient {
id: number id?: number
name: string name: string
description?: string description?: string
} }
export interface ICollection { export interface ICollection {
id: number id?: number
name: string name: string
active: string active: string
ismaincollection: boolean ismaincollection: boolean
ownerid: IUser["id"] ownerid?: IUser["id"]
} }
export interface IGroceryList { export interface IGroceryList {
id: number id?: number
name: string name: string
recipes?: IRecipe["id"][] recipes?: IRecipe["id"][]
active: boolean active: boolean
ownerid: IUser["id"] ownerid?: IUser["id"]
} }
export interface IUserAuth { export interface IUserAuth {