defined a form class
This commit is contained in:
29
client/package-lock.json
generated
29
client/package-lock.json
generated
@@ -11,11 +11,13 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.4.3",
|
||||
"sass": "^1.56.1"
|
||||
"sass": "^1.56.1",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.24",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@vitejs/plugin-react": "^2.2.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.2.3"
|
||||
@@ -555,6 +557,12 @@
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
||||
"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": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz",
|
||||
@@ -1629,6 +1637,14 @@
|
||||
"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": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz",
|
||||
@@ -2071,6 +2087,12 @@
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
||||
"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": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz",
|
||||
@@ -2713,6 +2735,11 @@
|
||||
"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": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz",
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.4.3",
|
||||
"sass": "^1.56.1"
|
||||
"sass": "^1.56.1",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.24",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@vitejs/plugin-react": "^2.2.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.2.3"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IUserAuth } from "../../schemas";
|
||||
import { attemptLogin } from "../../util/apiUtils";
|
||||
import { Button, Page, Panel } from "../ui";
|
||||
import Form, { FormConfig } from "../ui/Form";
|
||||
|
||||
export default function Login() {
|
||||
const [form, setForm] = useState<JSX.Element[]>();
|
||||
const [input, setInput] = useState<IUserAuth>({
|
||||
email: '',
|
||||
password: ''
|
||||
@@ -10,18 +13,31 @@ export default function Login() {
|
||||
|
||||
const handleLogin = async () => {
|
||||
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 (
|
||||
<Page>
|
||||
<h1>Hello! Nice to see you again.</h1>
|
||||
|
||||
<Panel extraStyles="form-panel">
|
||||
<label htmlFor="login-email">Email</label>
|
||||
<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>
|
||||
{ form }
|
||||
</Panel>
|
||||
|
||||
<aside>Not registered yet? You can do that <a href="/register">here.</a></aside>
|
||||
|
||||
@@ -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() {
|
||||
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 (
|
||||
<Page>
|
||||
<h1>Hi! Thanks for being here.</h1>
|
||||
|
||||
{/* divider */}
|
||||
<Divider />
|
||||
|
||||
<h2>Tell us a bit about yourself:</h2>
|
||||
|
||||
{/* auth form */}
|
||||
<Panel extraStyles="form-panel two-columns">
|
||||
{ form }
|
||||
</Panel>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
83
client/src/components/ui/Form.tsx
Normal file
83
client/src/components/ui/Form.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ const Navbar = () => {
|
||||
const navbarLoggedIn = (
|
||||
<div id="navbar">
|
||||
<div className="navbar-block">
|
||||
<h1>RECIPIN</h1>
|
||||
<a href="/">RECIPIN</a>
|
||||
</div>
|
||||
<div className="navbar-block">
|
||||
<p>Hi, {user}</p>
|
||||
@@ -24,7 +24,7 @@ const Navbar = () => {
|
||||
const navbarNotLoggedIn = (
|
||||
<div id="navbar">
|
||||
<div className="navbar-block">
|
||||
<h1>RECIPIN</h1>
|
||||
<a href="/">RECIPIN</a>
|
||||
</div>
|
||||
<div className='navbar-block'>
|
||||
<button>LOG IN</button>
|
||||
@@ -35,7 +35,7 @@ const Navbar = () => {
|
||||
const navbarRegistering = (
|
||||
<div id="navbar">
|
||||
<div className="navbar-block">
|
||||
<h1>RECIPIN</h1>
|
||||
<a href="/">RECIPIN</a>
|
||||
</div>
|
||||
<div className="navbar-block">
|
||||
<p>Hi, {user}</p>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
@import "../helpers/placeholders";
|
||||
|
||||
.Panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $papyrus;
|
||||
color: black;
|
||||
margin: 1rem 0;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { IUser } from "../schemas";
|
||||
import { IUser, IUserAuth } from "../schemas";
|
||||
const API = import.meta.env.APISTRING || "http://localhost:8080/";
|
||||
|
||||
// auth handlers
|
||||
export const attemptLogin = async (email: string, password: string) => {
|
||||
const result = await fetch(API + 'auth/login/', { method: "POST" })
|
||||
.then(response => response.json());
|
||||
export const attemptLogin = async (data: IUserAuth) => {
|
||||
const result: Array<keyof IUser> | null = await fetch(API + 'auth/login/', {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then(response => response.json());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
|
||||
router.post('/login', passport.authenticate('local'), async (req, res, next) => {
|
||||
try {
|
||||
const data: IUserAuth = req.body;
|
||||
console.log(data);
|
||||
const response = await AuthInstance.login(data);
|
||||
res.status(200).send(response);
|
||||
} catch(e) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface IUser {
|
||||
id: number
|
||||
id?: number
|
||||
firstname: string
|
||||
lastname: string
|
||||
handle: string
|
||||
@@ -9,34 +9,34 @@ export interface IUser {
|
||||
}
|
||||
|
||||
export interface IRecipe {
|
||||
id: number
|
||||
id?: number
|
||||
name: string
|
||||
description?: string
|
||||
preptime: string
|
||||
removed: boolean
|
||||
authoruserid: IUser["id"]
|
||||
authoruserid?: IUser["id"]
|
||||
}
|
||||
|
||||
export interface IIngredient {
|
||||
id: number
|
||||
id?: number
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface ICollection {
|
||||
id: number
|
||||
id?: number
|
||||
name: string
|
||||
active: string
|
||||
ismaincollection: boolean
|
||||
ownerid: IUser["id"]
|
||||
ownerid?: IUser["id"]
|
||||
}
|
||||
|
||||
export interface IGroceryList {
|
||||
id: number
|
||||
id?: number
|
||||
name: string
|
||||
recipes?: IRecipe["id"][]
|
||||
active: boolean
|
||||
ownerid: IUser["id"]
|
||||
ownerid?: IUser["id"]
|
||||
}
|
||||
|
||||
export interface IUserAuth {
|
||||
|
||||
Reference in New Issue
Block a user