remix-demo

react router (remix) demo

git clone https://9o.is/git/remix-demo.git

new.tsx

(3100B)


      1 import type {
      2   ActionArgs,
      3   LinksFunction,
      4   MetaFunction,
      5 } from "@remix-run/server-runtime";
      6 import { redirect } from "@remix-run/server-runtime";
      7 import Form from "~/components/forms/Form";
      8 import Input from "~/components/forms/Input";
      9 import PrimaryButton from "~/components/forms/PrimaryButton";
     10 import foodEntryController from "~/controllers/foodEntry.server";
     11 import {
     12   foodEntryUserValidator,
     13   foodEntryAdminValidator,
     14 } from "~/schemas/foodEntry";
     15 import { formatDateTime } from "~/utils/date";
     16 import { useState } from "react";
     17 import { debounce } from "throttle-debounce";
     18 import { useUser } from "~/utils/hooks";
     19 import { Role } from "@prisma/client";
     20 import comboboxStyles from "@reach/combobox/styles.css";
     21 import InputCombobox from "~/components/forms/InputCombobox";
     22 import Heading from "~/components/Heading";
     23 
     24 export const meta: MetaFunction = () => ({
     25   title: "Create Food Entry | Calories Counter",
     26 });
     27 
     28 export const links: LinksFunction = () => {
     29   return [{ rel: "stylesheet", href: comboboxStyles }];
     30 };
     31 
     32 export async function action({ request }: ActionArgs) {
     33   const response = await foodEntryController.create(request);
     34   if (response.ok) return redirect("..");
     35   else return response;
     36 }
     37 
     38 export default function FoodEntriesNew() {
     39   const user = useUser();
     40   const now = formatDateTime(new Date());
     41   const searchDebounced = debounce(250, autocompleteSearch);
     42   const [foodOptions, setFoodOptions] = useState([]);
     43   const [calories, setCalories] = useState(0);
     44 
     45   async function autocompleteSearch(query: string) {
     46     if (!query) return;
     47 
     48     const result = await fetch(`/api/foods?query=${encodeURIComponent(query)}`);
     49     setFoodOptions(await result.json());
     50   }
     51 
     52   async function findFoodByName(name: string) {
     53     const result = await fetch(`/api/foods/${name}`);
     54     const body = await result.json();
     55     setCalories(body.calories);
     56   }
     57 
     58   const validator =
     59     user.role === Role.ADMIN ? foodEntryAdminValidator : foodEntryUserValidator;
     60 
     61   return (
     62     <section>
     63       <Heading>Add Food Entry</Heading>
     64       <Form replace method="post" validator={validator} autoComplete="off">
     65         <InputCombobox
     66           label="Food Name"
     67           name="name"
     68           options={foodOptions}
     69           onInputChange={searchDebounced}
     70           onSelect={findFoodByName}
     71           required
     72         />
     73         <Input
     74           label="Calories"
     75           name="calories"
     76           type="number"
     77           min={0}
     78           max={5000}
     79           defaultValue={calories || undefined}
     80           required
     81         />
     82         <Input
     83           label="Date Consumed"
     84           name="consumed"
     85           type="datetime-local"
     86           defaultValue={now}
     87           max={now}
     88           required
     89         />
     90         {user.role === Role.ADMIN ? (
     91           <Input
     92             label="User ID"
     93             name="userId"
     94             type="text"
     95             defaultValue={user.id}
     96             required
     97           />
     98         ) : null}
     99         <PrimaryButton type="submit" className="w-full" data-cy="add-food">
    100           Add Food
    101         </PrimaryButton>
    102       </Form>
    103     </section>
    104   );
    105 }