extensible inputs on recipe add page

This commit is contained in:
Mikayla Dobson
2023-02-16 16:50:18 -06:00
parent 8f1cfa0ad9
commit 46454a84c2
21 changed files with 218 additions and 77 deletions

View File

@@ -51,7 +51,7 @@ const Friends: FC<{ targetUser?: IUser }> = ({ targetUser }) => {
<>
{ userList.length ?
(
<Card extraStyles="flex-row">
<Card extraClasses="flex-row">
<h2>Friends ({ userList?.length ?? "0" }):</h2>
<div className="friends-list">

View File

@@ -0,0 +1,47 @@
import { Autocomplete, TextField } from "@mui/material"
import { useRef, useState } from "react";
import { IIngredient } from "../../schemas";
import { Button } from "../ui";
interface IngredientSelectorProps {
position: number
ingredients: IIngredient[]
destroy: (position: number) => void
}
function IngredientSelector({ position, ingredients, destroy }: IngredientSelectorProps) {
const [options, setOptions] = useState(ingredients.map(each => each.name));
const [newOptions, setNewOptions] = useState(new Array<string>());
const [selected, setSelected] = useState(new Array<string>());
return (
<div style={{ display: "flex", flexDirection: "row" }}>
<Autocomplete
autoHighlight
options={options}
className="ui-creatable-component"
renderInput={(params) => (
<TextField
{...params}
variant="filled"
placeholder="Ingredient Name"
/>
)}
onKeyDown={(e) => {
if (e.code == 'Enter') {
const inputVal: string = e.target['value' as keyof EventTarget].toString();
console.log(inputVal)
if (inputVal.length) {
setSelected(prev => [...prev, inputVal])
setOptions((prev) => [...prev, inputVal]);
}
}
}}
/>
{/* @ts-ignore */}
<Button onClick={() => destroy(position)}>Close</Button>
</div>
)
}
export default IngredientSelector

View File

@@ -1,34 +1,29 @@
import { useCallback, useRef, useEffect, useState, createRef } from "react";
import { useAuthContext } from "../../context/AuthContext";
import { LegacyRef, MutableRefObject, useCallback, useEffect, useRef, useState } from "react";
import { Button, Card, Divider, Form, Page, Panel } from "../ui"
import { IIngredient, IRecipe } from "../../schemas";
import API from "../../util/API";
import Creatable from "react-select/creatable";
import { OptionType } from "../../util/types";
import { createOptionFromText, useSelectorContext } from "../../context/SelectorContext";
import { MultiValue } from "react-select";
import { Autocomplete, Chip, TextField } from "@mui/material";
import IngredientSelector from "../derived/IngredientSelector";
import { v4 } from "uuid";
const AddRecipe = () => {
const { user, token } = useAuthContext();
const {
data, setData, selector, setSelector,
options, setOptions, selected, setSelected,
onChange, onCreateOption
} = useSelectorContext();
const { data, setData, options, setOptions } = useSelectorContext();
const [ingredientFields, setIngredientFields] = useState<Array<JSX.Element>>([]);
const [triggerChange, setTriggerChange] = useState(false);
const [optionCount, setOptionCount] = useState(0);
const [form, setForm] = useState<JSX.Element>();
const [toast, setToast] = useState(<></>)
const [input, setInput] = useState<IRecipe>({ name: '', preptime: '', description: '', authoruserid: '' })
const initialIngredient = useRef(null);
// clear out selector state on page load
useEffect(() => {
/* useEffect(() => {
setData(new Array<IIngredient>());
setSelected(new Array<string>());
setOptions(new Array<OptionType>());
}, [])
}, []) */
// store all ingredients on page mount
useEffect(() => {
@@ -44,18 +39,14 @@ const AddRecipe = () => {
return { label: each.name, value: each.id }
}));
setOptionCount(result.length);
setIngredientFields([<IngredientSelector key={v4()} position={optionCount} ingredients={result} destroy={destroySelector} />]);
}
})();
}, [token])
useEffect(() => {
console.log(selected);
}, [selected])
useEffect(() => {
if (data.length) {
const autocompleteInstance = (
/* const autocompleteInstance = (
<Autocomplete
multiple
freeSolo
@@ -87,7 +78,7 @@ const AddRecipe = () => {
/>
)}
/>
)
) */
// create dropdown from new data
/*
@@ -102,13 +93,13 @@ const AddRecipe = () => {
/>
*/
data.length && setSelector(autocompleteInstance);
// data.length && setSelector(autocompleteInstance);
setTriggerChange(true);
}
}, [data, options, selected])
}, [data, options])
// once the dropdown data has populated, mount it within the full form
useEffect(() => {
/* useEffect(() => {
triggerChange && setForm(
<Form<IRecipe> _config={{
parent: "AddRecipe",
@@ -118,10 +109,9 @@ const AddRecipe = () => {
initialState: input,
getState: getFormState,
richTextInitialValue: "<p>Enter recipe details here!</p>",
selectorInstance: selector
}} />
)
}, [triggerChange])
}, [triggerChange]) */
useEffect(() => {
console.log(options);
@@ -139,6 +129,7 @@ const AddRecipe = () => {
}, [user])
// store input data from form
/*
const getFormState = useCallback((data: IRecipe) => {
setInput(data);
}, [input])
@@ -153,7 +144,11 @@ const AddRecipe = () => {
setSelected((prev) => {
return prev.filter(option => option !== target);
})
}
} */
useEffect(() => {
return;
}, [ingredientFields])
// submit handler
const handleCreate = async () => {
@@ -180,13 +175,69 @@ const AddRecipe = () => {
)
}
const destroySelector = useCallback((position: number) => {
setIngredientFields((prev) => {
const newState = new Array<JSX.Element>();
for (let i = 0; i < prev.length; i++) {
if (i === position) {
continue;
} else {
newState.push(prev[i]);
}
}
return newState;
})
}, [ingredientFields]);
function handleNewOption() {
setIngredientFields((prev) => [...prev, <IngredientSelector position={optionCount + 1} key={v4()} ingredients={data} destroy={destroySelector} />])
setOptionCount(prev => prev + 1);
}
useEffect(() => {
console.log(optionCount);
}, [optionCount])
return (
<Page>
<h1>Add a New Recipe</h1>
<Divider />
<Panel extraStyles="width-80">
{ form }
<Panel id="create-recipe-panel" extraClasses="ui-form-component width-80">
<div className="form-row">
<label>Recipe Name:</label>
<input />
</div>
<div className="form-row">
<label>Prep Time:</label>
<input />
</div>
<div className="form-row">
<label>Course:</label>
<input />
</div>
{ data && (
<Card extraClasses="form-row flex-row ingredient-card">
<label>Ingredients:</label>
<div id="ingredient-container">
{ ingredientFields }
<Button onClick={handleNewOption}>Add Ingredient</Button>
</div>
</Card>
)}
<Divider />
<div className="form-row">
<label>Description:</label>
{ "description here" }
</div>
<Button onClick={handleCreate}>Create Recipe!</Button>
<div id="toast">{ toast }</div>

View File

@@ -3,7 +3,7 @@ import { AuthContext, useAuthContext } from "../../context/AuthContext";
import { useNavigate, useParams } from "react-router-dom";
import { IUser, IUserAuth } from "../../schemas";
import { Button, Form, Page, Panel } from "../ui";
import { FormConfig } from "../ui/Form";
import { FormConfig } from "../ui/Form/Form";
import API from "../../util/API";
export default function Login() {
@@ -41,7 +41,7 @@ export default function Login() {
<Page>
<h1>Hello! Nice to see you again.</h1>
<Panel extraStyles="form-panel">
<Panel extraClasses="form-panel">
<Form<IUserAuth> _config={{
parent: 'login',

View File

@@ -6,7 +6,7 @@ import { IUser } from "../../../schemas";
import API from "../../../util/API";
import { Button, Page, Panel } from "../../ui";
import Divider from "../../ui/Divider";
import Form from "../../ui/Form";
import Form from "../../ui/Form/Form";
const blankUser: IUser = {
firstname: '',
@@ -52,7 +52,7 @@ const AboutYou: RegisterVariantType = ({ transitionDisplay }) => {
<h2>Tell us a bit about yourself:</h2>
<Panel extraStyles="form-panel two-columns">
<Panel extraClasses="form-panel two-columns">
<Form<IUser> _config={{
parent: "register",

View File

@@ -8,7 +8,7 @@ const Welcome = () => {
const { user } = useAuthContext();
const authUserActions = (
<Panel extraStyles="inherit-background c-papyrus uppercase flexrow">
<Panel extraClasses="inherit-background c-papyrus uppercase flexrow">
<Button onClick={() => navigate('/explore')}>Browse Recipes</Button>
<Button onClick={() => navigate('/subscriptions')}>Subscriptions</Button>
<Button onClick={() => navigate('/grocery-list')}>Grocery Lists</Button>
@@ -16,27 +16,27 @@ const Welcome = () => {
)
const callToRegister = (
<Panel extraStyles="inherit-background c-papyrus uppercase">
<Panel extraClasses="inherit-background c-papyrus uppercase">
<h2>Ready to get started?</h2>
<Button onClick={() => navigate('/register')}>Register</Button>
</Panel>
)
return (
<Page extraStyles="narrow-dividers">
<Panel extraStyles='inherit-background c-papyrus uppercase'>
<Page extraClasses="narrow-dividers">
<Panel extraClasses='inherit-background c-papyrus uppercase'>
<h1>Welcome to Recipin</h1>
</Panel>
<Divider />
<Panel extraStyles="inherit-background c-papyrus uppercase">
<Panel extraClasses="inherit-background c-papyrus uppercase">
<h2>Simple Recipe Management and Sharing for the Home</h2>
</Panel>
<Divider />
<Panel extraStyles="inherit-background c-papyrus uppercase">
<Panel extraClasses="inherit-background c-papyrus uppercase">
<h2>Build Shopping Lists Directly from Your Recipes</h2>
</Panel>

View File

@@ -1,6 +1,6 @@
import { FC, useEffect, useState } from "react";
import Protect from "../../util/Protect";
import Form from "./Form";
import Form from "./Form/Form";
interface BrowserProps {
children?: JSX.Element[]

View File

@@ -1,9 +1,9 @@
import { ButtonComponent } from "../../util/types"
import "/src/sass/components/Button.scss";
const Button: ButtonComponent = ({ onClick = (() => {}), children, extraStyles, disabled = false, disabledText = null }) => {
const Button: ButtonComponent = ({ onClick = (() => {}), children, extraClasses, disabled = false, disabledText = null }) => {
return (
<button onClick={onClick} disabled={disabled} className={`ui-button ${extraStyles || ''}`}>
<button onClick={onClick} disabled={disabled} className={`ui-button ${extraClasses || ''}`}>
{ disabled ? (disabledText || children || "Button") : (children || "Button") }
</button>
)

View File

@@ -1,9 +1,9 @@
import { FC } from "react";
const Card: FC<{ children?: JSX.Element | JSX.Element[], extraStyles?: string }> = ({ children = <></>, extraStyles = ""}) => {
const Card: FC<{ children?: JSX.Element | JSX.Element[], extraClasses?: string }> = ({ children = <></>, extraClasses = ""}) => {
return (
<div className={`ui-card ${extraStyles}`}>
<div className={`ui-card ${extraClasses}`}>
{ Array.isArray(children) ? <>{children}</> : children }
</div>
)

View File

@@ -4,9 +4,9 @@ import { PortalBase } from "../../util/types";
import "/src/sass/components/Dropdown.scss";
// expects to receive buttons as children
const Dropdown: FC<PortalBase> = ({ children, extraStyles = null }) => {
const Dropdown: FC<PortalBase> = ({ children, extraClasses = null }) => {
return (
<Panel extraStyles={`ui-dropdown ${extraStyles}`}>
<Panel extraClasses={`ui-dropdown ${extraClasses}`}>
{ children }
</Panel>
)

View File

@@ -1,12 +1,12 @@
import { ChangeEvent, FC, useEffect, useState } from "react"
import { v4 } from "uuid"
import { useAuthContext } from "../../context/AuthContext";
import { useSelectorContext } from "../../context/SelectorContext";
import SelectorProvider from "../../context/SelectorProvider";
import { IIngredient, IUser } from "../../schemas";
import API from "../../util/API";
import RichText from "./RichText"
import Selector from "./Selector";
import { useAuthContext } from "../../../context/AuthContext";
import { useSelectorContext } from "../../../context/SelectorContext";
import SelectorProvider from "../../../context/SelectorProvider";
import { IIngredient, IUser } from "../../../schemas";
import API from "../../../util/API";
import RichText from "../RichText"
import Selector from "../Selector";
import "/src/sass/components/Form.scss";
export interface FormConfig<T> {
@@ -17,7 +17,7 @@ export interface FormConfig<T> {
labels?: string[]
dataTypes?: string[]
richTextInitialValue?: string
extraStyles?: string
extraClasses?: string
selectorInstance?: JSX.Element
}
@@ -132,7 +132,7 @@ function Form<T>({ _config }: FormProps) {
}, [config]);
return (
<div className={`ui-form-component ${_config.extraStyles ?? ""}`}>
<div className={`ui-form-component ${_config.extraClasses ?? ""}`}>
{ contents }
</div>
)

View File

@@ -0,0 +1,38 @@
import { useEffect, useState } from "react";
import { v4 } from "uuid";
interface FormRowConfig {
parent: any
labelText: string
idx?: number
dataType?: string
}
function FormRow({ parent, labelText, idx, dataType = "string" }) {
const [row, setRow] = useState<JSX.Element>();
useEffect(() => {
switch (dataType) {
case "TINYMCE":
break;
case "string":
default:
setRow(
<div className="form-row" id={`${parent}-row-${idx || v4()}`} key={v4()}>
<label htmlFor={`${parent}-${each}`}>{labelText}</label>
<input
type={dataType}
id={`${parent}-${each}`}
onChange={(e) => update(e, idx)}
value={state[i as keyof T] as string}>
</input>
</div>
)
break;
}
}, [])
return (
<></>
)
}

View File

@@ -53,7 +53,7 @@ const LoggedIn = () => {
</div>
{
dropdownActive && (
<Dropdown extraStyles="top-menu-bar actions-bar">
<Dropdown extraClasses="top-menu-bar actions-bar">
<Button onClick={() => handleOptionSelect('/add-recipe')}>Add a Recipe</Button>
<Button onClick={() => handleOptionSelect("/add-friends")}>Add Friends</Button>
<Button onClick={() => handleOptionSelect('/collections')}>My Collections</Button>
@@ -65,7 +65,7 @@ const LoggedIn = () => {
}
{
searchActive && (
<Dropdown extraStyles="top-menu-bar search-bar">
<Dropdown extraClasses="top-menu-bar search-bar">
<Button>Run Search</Button>
</Dropdown>
)

View File

@@ -5,10 +5,10 @@ import { PageComponent } from "../../util/types"
import Navbar from "./Navbar";
import "/src/sass/components/Page.scss";
const Page: PageComponent = ({ extraStyles, children }) => {
const Page: PageComponent = ({ extraClasses, children }) => {
return (
<main id="view">
<section className={`Page ${extraStyles || null}`}>
<section className={`Page ${extraClasses || null}`}>
{ children || null }
</section>
</main>

View File

@@ -1,12 +1,20 @@
import { PanelComponent } from "../../util/types";
import "/src/sass/components/Panel.scss";
const Panel: PanelComponent = ({ children, extraStyles }) => {
const Panel: PanelComponent = ({ children, extraClasses, id }) => {
if (id) {
return (
<div className={`Panel ${extraStyles || ''}`}>
<div id={id} className={`Panel ${extraClasses || ''}`}>
{ children || null }
</div>
)
} else {
return (
<div className={`Panel ${extraClasses || ''}`}>
{ children || null }
</div>
)
}
}
export default Panel;

View File

@@ -1,4 +1,4 @@
import { FormConfig } from "./Form"
import { FormConfig } from "./Form/Form"
interface OptionType {
value: number

View File

@@ -1,9 +1,9 @@
import { FC } from "react";
import { PortalBase } from "../../util/types";
const Tooltip: FC<PortalBase> = ({ children, extraStyles = null }) => {
const Tooltip: FC<PortalBase> = ({ children, extraClasses = null }) => {
return (
<aside className={`ui-tooltip ${extraStyles}`}>
<aside className={`ui-tooltip ${extraClasses}`}>
{ children }
</aside>
)

View File

@@ -6,7 +6,7 @@ import API from "../../util/API";
import Button from "./Button";
import Card from "./Card";
const UserCard: UserCardType = ({ extraStyles, targetUser }) => {
const UserCard: UserCardType = ({ extraClasses, targetUser }) => {
const [buttonVariant, setButtonVariant] = useState(<></>);
const { token } = useAuthContext();
@@ -45,7 +45,7 @@ const UserCard: UserCardType = ({ extraStyles, targetUser }) => {
}
return (
<Card extraStyles={'user-card' + extraStyles}>
<Card extraClasses={'user-card' + extraClasses}>
<>
<div className="avatar"></div>
<h3><a href={`/profile?id=${targetUser.id}`}>{targetUser.firstname} {targetUser.lastname.substring(0,1)}.</a></h3>

View File

@@ -2,7 +2,7 @@ import Button from "./Button";
import Card from "./Card";
import Divider from "./Divider";
import Dropdown from "./Dropdown";
import Form from "./Form";
import Form from "./Form/Form";
import Navbar from "./Navbar";
import Page from "./Page";
import Panel from "./Panel";

View File

@@ -7,7 +7,3 @@
text-align: center;
background-color: $richblack;
}
.ui-creatable-component {
margin-bottom: 10rem;
}

View File

@@ -5,7 +5,8 @@ import { IUser } from "../schemas";
export interface PortalBase {
children?: ReactNode | ReactNode[]
extraStyles?: string
extraClasses?: string
id?: string
}
interface ButtonParams extends PortalBase {