bit of work on the form component
This commit is contained in:
@@ -54,11 +54,13 @@ const Friends: FC<{ targetUser?: IUser }> = ({ targetUser }) => {
|
|||||||
<Card extraStyles="flex-row">
|
<Card extraStyles="flex-row">
|
||||||
<h2>Friends ({ userList?.length ?? "0" }):</h2>
|
<h2>Friends ({ userList?.length ?? "0" }):</h2>
|
||||||
|
|
||||||
|
<div className="friends-list">
|
||||||
{
|
{
|
||||||
userList.map((user: IUser) => {
|
userList.map((user: IUser) => {
|
||||||
return <UserCard key={v4()} targetUser={user} />
|
return <UserCard key={v4()} targetUser={user} />
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<p>Looking for someone else?</p>
|
<p>Looking for someone else?</p>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const AddRecipe = () => {
|
|||||||
parent: "AddRecipe",
|
parent: "AddRecipe",
|
||||||
keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"],
|
keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"],
|
||||||
labels: ["Recipe Name:", "Prep Time:", "Course:", "Cuisine:", "Ingredients:", "Description:"],
|
labels: ["Recipe Name:", "Prep Time:", "Course:", "Cuisine:", "Ingredients:", "Description:"],
|
||||||
dataTypes: ['text', 'text', 'custom picker', 'custom picker', 'custom picker', 'TINYMCE'],
|
dataTypes: ['text', 'text', 'custom picker', 'custom picker', 'SELECTOR', 'TINYMCE'],
|
||||||
initialState: input,
|
initialState: input,
|
||||||
getState: getFormState,
|
getState: getFormState,
|
||||||
richTextInitialValue: "<p>Enter recipe details here!</p>"
|
richTextInitialValue: "<p>Enter recipe details here!</p>"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { FC } from "react"
|
import { FC } from "react";
|
||||||
import { MultiChildPortal } from "../../util/types"
|
|
||||||
|
|
||||||
const Card: FC<MultiChildPortal> = ({ children = <></>, extraStyles = ""}) => {
|
|
||||||
|
const Card: FC<{ children?: JSX.Element | JSX.Element[], extraStyles?: string }> = ({ children = <></>, extraStyles = ""}) => {
|
||||||
return (
|
return (
|
||||||
<div className={`ui-card ${extraStyles}`}>
|
<div className={`ui-card ${extraStyles}`}>
|
||||||
{ children }
|
{ Array.isArray(children) ? <>{children}</> : children }
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { ChangeEvent, FC, useEffect, useState } from "react"
|
import { ChangeEvent, FC, useEffect, useState } from "react"
|
||||||
import { v4 } from "uuid"
|
import { v4 } from "uuid"
|
||||||
|
import { useAuthContext } from "../../context/AuthContext";
|
||||||
|
import { IIngredient, IUser } from "../../schemas";
|
||||||
|
import API from "../../util/API";
|
||||||
import RichText from "./RichText"
|
import RichText from "./RichText"
|
||||||
|
import Selector from "./Selector";
|
||||||
import "/src/sass/components/Form.scss";
|
import "/src/sass/components/Form.scss";
|
||||||
|
|
||||||
export interface FormConfig<T> {
|
export interface FormConfig<T> {
|
||||||
@@ -27,6 +31,8 @@ const Form: FC<FormProps> = ({ parent, _config }) => {
|
|||||||
const [state, setState] = useState<T>();
|
const [state, setState] = useState<T>();
|
||||||
const [contents, setContents] = useState<JSX.Element[]>();
|
const [contents, setContents] = useState<JSX.Element[]>();
|
||||||
|
|
||||||
|
const { token } = useAuthContext();
|
||||||
|
|
||||||
// initial setup
|
// initial setup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!config) setConfig({
|
if (!config) setConfig({
|
||||||
@@ -66,35 +72,61 @@ const Form: FC<FormProps> = ({ parent, _config }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function populateSelector(key: string): Promise<any[] | null> {
|
||||||
|
if (!token) return null;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "ingredient":
|
||||||
|
const ingredients = new API.Ingredient(token);
|
||||||
|
const result = await ingredients.getAll();
|
||||||
|
if (result) return result;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// mount the form once config has been loaded
|
// mount the form once config has been loaded
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state && config) {
|
if (state && config) {
|
||||||
const result = config.keys.map((each: string, i: number) => {
|
(async() => {
|
||||||
|
const result = config.keys.map(async (each: string, i: number) => {
|
||||||
if (config.dataTypes![i] == 'TINYMCE') {
|
if (config.dataTypes![i] == 'TINYMCE') {
|
||||||
return (
|
return (
|
||||||
<div className="form-row-editor" id={`${config.parent}-row-${i}`} key={v4()}>
|
<div className="form-row-editor" id={`${config.parent}-row-${i}`} key={v4()}>
|
||||||
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
|
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
|
||||||
<RichText id={`${config.parent}-${each}`} initialValue={config.richTextInitialValue} getState={(txt) => updateRichText(txt, i)} />
|
<RichText id={`${config.parent}-${each}`} initialValue={config.richTextInitialValue} getState={(txt) => updateRichText(txt, i)} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
} else {
|
} else if (config.dataTypes![i] == 'SELECTOR') {
|
||||||
return (
|
type StrongType = Partial<T> & { id: number, name: string };
|
||||||
<div className="form-row" id={`${config.parent}-row-${i}`} key={v4()}>
|
const storedResult = await (async() => {
|
||||||
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
|
const result = await populateSelector(config?.labels![i] || "");
|
||||||
<input
|
if (result) return result as T[];
|
||||||
type={config.dataTypes![i]}
|
return null;
|
||||||
id={`${config.parent}-${each}`}
|
})();
|
||||||
onChange={(e) => update(e, i)}
|
|
||||||
value={state[i as keyof T] as string}>
|
return <Selector<StrongType> optionList={storedResult || []} />
|
||||||
</input>
|
} else {
|
||||||
</div>
|
return (
|
||||||
)
|
<div className="form-row" id={`${config.parent}-row-${i}`} key={v4()}>
|
||||||
}
|
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
|
||||||
});
|
<input
|
||||||
|
type={config.dataTypes![i]}
|
||||||
setContents(result);
|
id={`${config.parent}-${each}`}
|
||||||
|
onChange={(e) => update(e, i)}
|
||||||
|
value={state[i as keyof T] as string}>
|
||||||
|
</input>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mappedContents = await Promise.all(result);
|
||||||
|
mappedContents && setContents(mappedContents);
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
|
|||||||
17
client/src/components/ui/Selector.tsx
Normal file
17
client/src/components/ui/Selector.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
interface Entity {
|
||||||
|
id: string | number
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Selector<T extends Entity>({ optionList }: { optionList: Array<T> }) {
|
||||||
|
// const Selector: FC<{ optionList: Array<T extends HasID> }> = ({ optionList }) => {
|
||||||
|
return (
|
||||||
|
<select className="ui-select-component">
|
||||||
|
{ optionList.map(item =>
|
||||||
|
<option id={`select-item-${item.name}-${item.id}`}>{item.name}</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Selector
|
||||||
@@ -46,10 +46,12 @@ const UserCard: UserCardType = ({ extraStyles, targetUser }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card extraStyles={'user-card' + extraStyles}>
|
<Card extraStyles={'user-card' + extraStyles}>
|
||||||
|
<>
|
||||||
<div className="avatar"></div>
|
<div className="avatar"></div>
|
||||||
<h3><a href={`/profile?id=${targetUser.id}`}>{targetUser.firstname} {targetUser.lastname.substring(0,1)}.</a></h3>
|
<h3><a href={`/profile?id=${targetUser.id}`}>{targetUser.firstname} {targetUser.lastname.substring(0,1)}.</a></h3>
|
||||||
<h4>@{targetUser.handle}</h4>
|
<h4>@{targetUser.handle}</h4>
|
||||||
{ buttonVariant }
|
{ buttonVariant }
|
||||||
|
</>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ module API {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async customRoute(method: CRUDMETHOD, path: string, data?: any, requireHeaders = true) {
|
protected async customRoute(method: CRUDMETHOD, path: string, data?: any, requireHeaders = true) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case CRUDMETHOD.GET:
|
case CRUDMETHOD.GET:
|
||||||
return this.instance.get(this.endpoint + path, (requireHeaders && this.headers));
|
return this.instance.get(this.endpoint + path, (requireHeaders && this.headers));
|
||||||
@@ -186,7 +186,6 @@ module API {
|
|||||||
|
|
||||||
export class Ingredient extends RestController<IIngredient> {
|
export class Ingredient extends RestController<IIngredient> {
|
||||||
constructor(token: string) {
|
constructor(token: string) {
|
||||||
if (!token) throw new Error("Missing required token");
|
|
||||||
super(Settings.getAPISTRING() + "/app/ingredients", token);
|
super(Settings.getAPISTRING() + "/app/ingredients", token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ export interface ProtectParams extends PortalBase {
|
|||||||
|
|
||||||
interface UserCardProps extends PortalBase {
|
interface UserCardProps extends PortalBase {
|
||||||
targetUser: IUser
|
targetUser: IUser
|
||||||
canAdd?: boolean
|
|
||||||
liftData?: (data: any) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
|
|||||||
Reference in New Issue
Block a user