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": "^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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
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 = (
|
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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user