From 5170d883ccbd8a6732827af274adf13a68e331c8 Mon Sep 17 00:00:00 2001
From: Brandon Egger <brandonegger64@gmail.com>
Date: Sun, 4 Jun 2023 22:30:07 -0500
Subject: [PATCH] add multiselector form

---
 src/components/ResourceTable.tsx        |  36 +-------
 src/components/admin/resources/form.tsx | 109 ++++++++++++++++--------
 src/components/forms/selectors.tsx      |  61 +++++++++++++
 src/components/forms/textInput.tsx      |  38 +++++++++
 src/prices/Icons.tsx                    |  35 ++++++++
 5 files changed, 211 insertions(+), 68 deletions(-)
 create mode 100644 src/components/forms/selectors.tsx
 create mode 100644 src/components/forms/textInput.tsx
 create mode 100644 src/prices/Icons.tsx

diff --git a/src/components/ResourceTable.tsx b/src/components/ResourceTable.tsx
index f6ce3a9..9d15515 100644
--- a/src/components/ResourceTable.tsx
+++ b/src/components/ResourceTable.tsx
@@ -1,15 +1,10 @@
 import {
   type PlatformLink,
-  type PaymentType,
   type AuditoryResource,
   type Skill,
   type SkillLevel,
   type Manufacturer,
 } from "@prisma/client";
-import {
-  CurrencyDollarIcon,
-  ArrowPathRoundedSquareIcon,
-} from "@heroicons/react/24/solid";
 import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
 import Image from "next/image";
 import Link from "next/link";
@@ -18,6 +13,7 @@ import { type ChangeEvent } from "react";
 import { ChevronDownIcon } from "@heroicons/react/24/outline";
 import { type ParsedUrlQuery, type ParsedUrlQueryInput } from "querystring";
 import { useRouter } from "next/router";
+import { PriceIcon } from "~/prices/Icons";
 
 export const ResourceInfo = ({
   resource,
@@ -26,34 +22,6 @@ export const ResourceInfo = ({
   resource: AuditoryResource;
   showMoreInfo?: boolean;
 }) => {
-  const PriceIcons = ({ type }: { type: PaymentType }) => {
-    switch (type) {
-      case "FREE": {
-        return (
-          <div className="space-x-1 pt-2" title="Free">
-            <span className="rounded-lg border border-neutral-900 bg-amber-100 px-2 py-[1px] italic text-black">
-              free
-            </span>
-          </div>
-        );
-      }
-      case "SUBSCRIPTION_MONTHLY": {
-        <div className="space-x-1" title="Monthly recurring subscription">
-          <ArrowPathRoundedSquareIcon className="inline h-6 w-6" />
-          <CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
-        </div>;
-      }
-      case "SUBSCRIPTION_WEEKLY": {
-        return (
-          <div className="space-x-1" title="Weekly recurring subscription">
-            <ArrowPathRoundedSquareIcon className="inline h-6 w-6" />
-            <CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
-          </div>
-        );
-      }
-    }
-  };
-
   const PlatformInfo = ({
     platformLinks,
   }: {
@@ -105,7 +73,7 @@ export const ResourceInfo = ({
           </h2>
           <h1 className="text-xl font-bold">{resource.name}</h1>
           <PlatformInfo platformLinks={resource.platform_links} />
-          <PriceIcons type={resource?.payment_options[0] ?? "FREE"} />
+          <PriceIcon type={resource?.payment_options[0] ?? "FREE"} />
         </div>
       </div>
     </div>
diff --git a/src/components/admin/resources/form.tsx b/src/components/admin/resources/form.tsx
index ba1f2f2..3ab1603 100644
--- a/src/components/admin/resources/form.tsx
+++ b/src/components/admin/resources/form.tsx
@@ -1,7 +1,13 @@
-import { type AuditoryResource } from "@prisma/client";
+import { PaymentType, type AuditoryResource } from "@prisma/client";
 import Image from "next/image";
 import { PencilSquareIcon } from "@heroicons/react/24/solid";
-import { useState } from "react";
+import {
+  MultiSelector,
+  MultiSelectorContext,
+  MultiSelectorOption,
+} from "../../forms/selectors";
+import { InfoInputLine } from "~/components/forms/textInput";
+import { PriceIcon } from "~/prices/Icons";
 
 /**
  * Renders the image selector for resource form.
@@ -56,29 +62,38 @@ const ResourceLinkSubForm = ({}) => {
   );
 };
 
-/**
- * Single line input for the fields found to the right of the
- * resource logo.
- */
-const InfoInputLine = ({
-  value,
-  placeholder,
+const PaymentTypeOption = ({
+  type,
+  label,
 }: {
-  value: string;
-  placeholder: string;
+  type: PaymentType;
+  label: string;
 }) => {
-  const [currentValue, setCurrentValue] = useState<string>(value);
-
   return (
-    <input
-      onChange={(event) => {
-        setCurrentValue(event.target.value);
-      }}
-      placeholder={placeholder}
-      value={currentValue}
-      type="text"
-      className="w-full border-b border-neutral-300 px-2"
-    />
+    <MultiSelectorOption value={type}>
+      <MultiSelectorContext.Consumer>
+        {({ selected }) => (
+          <div
+            className={
+              (selected === type ? "bg-stone-800" : "bg-white") +
+              " flex flex-row space-x-2 whitespace-nowrap rounded-xl border border-neutral-400 py-[1px] pl-[1px] pr-2"
+            }
+          >
+            <span className="rounded-[10px] bg-white p-1">
+              <PriceIcon type={type} />
+            </span>
+            <span
+              className={
+                (selected === type ? "text-white" : "text black") +
+                " my-auto inline-block whitespace-nowrap text-sm"
+              }
+            >
+              {label}
+            </span>
+          </div>
+        )}
+      </MultiSelectorContext.Consumer>
+    </MultiSelectorOption>
   );
 };
 
@@ -91,41 +106,67 @@ const ResourceSummarySubForm = ({
   resource?: AuditoryResource;
 }) => {
   return (
-    <>
-      <div className="flex flex-row space-x-4 p-4">
+    <div className="space-y-4 px-4">
+      <div className="flex flex-row space-x-4 pt-4">
         <div className="flex w-20 flex-col justify-center space-y-2 sm:w-28">
           <SelectImageInput file={resource?.icon} />
         </div>
         <div className="w-full overflow-hidden rounded-xl border border-neutral-400 bg-white drop-shadow-lg">
-          <span className="text-sm">
+          <span className="text-md">
             <InfoInputLine
               placeholder="manufacturer"
               value={resource?.manufacturer?.name ?? ""}
+              hint="manufacturer"
             />
           </span>
-          <InfoInputLine placeholder="name" value={resource?.name ?? ""} />
-          <span className="my-2 block w-full text-center text-sm italic text-neutral-400">
+          <InfoInputLine
+            placeholder="name"
+            value={resource?.name ?? ""}
+            hint="name"
+          />
+          <span className="my-1 block w-full text-center text-xs italic text-neutral-400">
             Edit the fields above
           </span>
         </div>
       </div>
-      <div className="mx-4 overflow-hidden rounded-xl border border-neutral-400 bg-neutral-200 text-left shadow"></div>
-      <div className="ml-4 mr-auto mt-4 rounded-lg border-2 border-neutral-900 bg-neutral-600">
-        <span className="px-2 py-2 text-sm text-neutral-200">
-          Ages {/** Age range here */}
-        </span>
+      <div>
+        <MultiSelector
+          label="Price Category"
+          defaultValue={
+            resource?.payment_options.toString() ?? PaymentType.FREE.toString()
+          }
+        >
+          <PaymentTypeOption type={PaymentType.FREE} label="Free" />
+          <PaymentTypeOption
+            type={PaymentType.SUBSCRIPTION_MONTHLY}
+            label="Monthly Subscription"
+          />
+          <PaymentTypeOption
+            type={PaymentType.SUBSCRIPTION_WEEKLY}
+            label="Weekly Subscription"
+          />
+        </MultiSelector>
       </div>
-    </>
+    </div>
   );
 };
 
+// TODO:
+// const ResourceDescriptionSubForm = ({
+//   resource,
+// }: {
+//   resource?: AuditoryResource;
+// }) => {
+//   return <div></div>;
+// };
+
 const ResourceForm = ({ resource }: { resource?: AuditoryResource }) => {
   return (
     <div className="mx-auto flex max-w-2xl flex-col flex-col-reverse divide-x py-4 sm:flex-row">
       <div className="my-5 mr-4 flex flex-col justify-end text-lg font-bold">
         <ResourceLinkSubForm /> {/** //resource={resource} /> */}
       </div>
-      <div className="justify-left flex flex-col pb-5">
+      <div className="justify-left flex max-w-lg flex-col pb-5">
         <ResourceSummarySubForm resource={resource} />
       </div>
     </div>
diff --git a/src/components/forms/selectors.tsx b/src/components/forms/selectors.tsx
new file mode 100644
index 0000000..e258099
--- /dev/null
+++ b/src/components/forms/selectors.tsx
@@ -0,0 +1,61 @@
+import { createContext, useContext, useState } from "react";
+
+// Define contexts
+const MultiSelectorContext = createContext({
+  selected: "",
+  updateCallback: (_value: string) => {
+    return;
+  },
+});
+
+function MultiSelector<T extends { toString: () => string }>({
+  label,
+  defaultValue,
+  children,
+}: {
+  label: string;
+  defaultValue: T;
+  children: undefined | JSX.Element | JSX.Element[];
+}) {
+  const [selected, setSelected] = useState<string>(defaultValue.toString());
+
+  return (
+    <MultiSelectorContext.Provider
+      value={{ selected, updateCallback: setSelected }}
+    >
+      <div className="flex flex-col">
+        <label className="text-md block font-semibold">{label}</label>
+        <span className="block text-sm italic text-neutral-400">
+          Select one from below
+        </span>
+        <input readOnly type="text" className="hidden" value={selected ?? ""} />
+        <div className="mt-2 flex flex-row space-x-2 overflow-x-auto">
+          {children}
+        </div>
+      </div>
+    </MultiSelectorContext.Provider>
+  );
+}
+
+function MultiSelectorOption<T extends { toString: () => string }>({
+  value,
+  children,
+}: {
+  value: T;
+  children: undefined | JSX.Element | JSX.Element[];
+}) {
+  const { updateCallback } = useContext(MultiSelectorContext);
+
+  return (
+    <button
+      type="button"
+      onClick={() => {
+        updateCallback?.(value.toString());
+      }}
+    >
+      {children}
+    </button>
+  );
+}
+
+export { MultiSelectorContext, MultiSelector, MultiSelectorOption };
diff --git a/src/components/forms/textInput.tsx b/src/components/forms/textInput.tsx
new file mode 100644
index 0000000..21a92ac
--- /dev/null
+++ b/src/components/forms/textInput.tsx
@@ -0,0 +1,38 @@
+import { useState } from "react";
+
+/**
+ * Single line input for the fields found to the right of the
+ * resource logo.
+ */
+const InfoInputLine = ({
+  value,
+  placeholder,
+  hint,
+}: {
+  value: string;
+  placeholder: string;
+  hint?: string;
+}) => {
+  const [currentValue, setCurrentValue] = useState<string>(value);
+
+  return (
+    <div className="relative">
+      <input
+        onChange={(event) => {
+          setCurrentValue(event.target.value);
+        }}
+        placeholder={placeholder}
+        value={currentValue}
+        type="text"
+        className="w-full border-b border-neutral-300 px-2"
+      />
+      <label className="absolute bottom-0 right-2 top-0 text-right text-sm">
+        {currentValue !== "" ? (
+          <span className="italic text-neutral-400">{hint}</span>
+        ) : undefined}
+      </label>
+    </div>
+  );
+};
+
+export { InfoInputLine };
diff --git a/src/prices/Icons.tsx b/src/prices/Icons.tsx
new file mode 100644
index 0000000..3a6f4bf
--- /dev/null
+++ b/src/prices/Icons.tsx
@@ -0,0 +1,35 @@
+import { type PaymentType } from "@prisma/client";
+import {
+  CurrencyDollarIcon,
+  ArrowPathRoundedSquareIcon,
+} from "@heroicons/react/24/solid";
+
+const PriceIcon = ({ type }: { type: PaymentType }) => {
+  switch (type) {
+    case "FREE": {
+      return (
+        <div className="space-x-1" title="Free">
+          <span className="rounded-lg border border-neutral-900 bg-amber-100 px-2 py-[1px] italic text-black">
+            free
+          </span>
+        </div>
+      );
+    }
+    case "SUBSCRIPTION_MONTHLY": {
+      <div className="space-x-1" title="Monthly recurring subscription">
+        <ArrowPathRoundedSquareIcon className="inline-block h-6 w-6" />
+        <CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
+      </div>;
+    }
+    case "SUBSCRIPTION_WEEKLY": {
+      return (
+        <div className="space-x-1" title="Weekly recurring subscription">
+          <ArrowPathRoundedSquareIcon className="inline-block h-6 w-6" />
+          <CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
+        </div>
+      );
+    }
+  }
+};
+
+export { PriceIcon };