add multiselector form
This commit is contained in:
parent
026e7917ed
commit
5170d883cc
@ -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>
|
||||
|
@ -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>
|
||||
|
61
src/components/forms/selectors.tsx
Normal file
61
src/components/forms/selectors.tsx
Normal file
@ -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 };
|
38
src/components/forms/textInput.tsx
Normal file
38
src/components/forms/textInput.tsx
Normal file
@ -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 };
|
35
src/prices/Icons.tsx
Normal file
35
src/prices/Icons.tsx
Normal file
@ -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 };
|
Loading…
x
Reference in New Issue
Block a user