add multiselector form
This commit is contained in:
parent
026e7917ed
commit
5170d883cc
@ -1,15 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
type PlatformLink,
|
type PlatformLink,
|
||||||
type PaymentType,
|
|
||||||
type AuditoryResource,
|
type AuditoryResource,
|
||||||
type Skill,
|
type Skill,
|
||||||
type SkillLevel,
|
type SkillLevel,
|
||||||
type Manufacturer,
|
type Manufacturer,
|
||||||
} from "@prisma/client";
|
} from "@prisma/client";
|
||||||
import {
|
|
||||||
CurrencyDollarIcon,
|
|
||||||
ArrowPathRoundedSquareIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
|
import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@ -18,6 +13,7 @@ import { type ChangeEvent } from "react";
|
|||||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||||
import { type ParsedUrlQuery, type ParsedUrlQueryInput } from "querystring";
|
import { type ParsedUrlQuery, type ParsedUrlQueryInput } from "querystring";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { PriceIcon } from "~/prices/Icons";
|
||||||
|
|
||||||
export const ResourceInfo = ({
|
export const ResourceInfo = ({
|
||||||
resource,
|
resource,
|
||||||
@ -26,34 +22,6 @@ export const ResourceInfo = ({
|
|||||||
resource: AuditoryResource;
|
resource: AuditoryResource;
|
||||||
showMoreInfo?: boolean;
|
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 = ({
|
const PlatformInfo = ({
|
||||||
platformLinks,
|
platformLinks,
|
||||||
}: {
|
}: {
|
||||||
@ -105,7 +73,7 @@ export const ResourceInfo = ({
|
|||||||
</h2>
|
</h2>
|
||||||
<h1 className="text-xl font-bold">{resource.name}</h1>
|
<h1 className="text-xl font-bold">{resource.name}</h1>
|
||||||
<PlatformInfo platformLinks={resource.platform_links} />
|
<PlatformInfo platformLinks={resource.platform_links} />
|
||||||
<PriceIcons type={resource?.payment_options[0] ?? "FREE"} />
|
<PriceIcon type={resource?.payment_options[0] ?? "FREE"} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 Image from "next/image";
|
||||||
import { PencilSquareIcon } from "@heroicons/react/24/solid";
|
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.
|
* Renders the image selector for resource form.
|
||||||
@ -56,29 +62,38 @@ const ResourceLinkSubForm = ({}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const PaymentTypeOption = ({
|
||||||
* Single line input for the fields found to the right of the
|
type,
|
||||||
* resource logo.
|
label,
|
||||||
*/
|
|
||||||
const InfoInputLine = ({
|
|
||||||
value,
|
|
||||||
placeholder,
|
|
||||||
}: {
|
}: {
|
||||||
value: string;
|
type: PaymentType;
|
||||||
placeholder: string;
|
label: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [currentValue, setCurrentValue] = useState<string>(value);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<input
|
<MultiSelectorOption value={type}>
|
||||||
onChange={(event) => {
|
<MultiSelectorContext.Consumer>
|
||||||
setCurrentValue(event.target.value);
|
{({ selected }) => (
|
||||||
}}
|
<div
|
||||||
placeholder={placeholder}
|
className={
|
||||||
value={currentValue}
|
(selected === type ? "bg-stone-800" : "bg-white") +
|
||||||
type="text"
|
" flex flex-row space-x-2 whitespace-nowrap rounded-xl border border-neutral-400 py-[1px] pl-[1px] pr-2"
|
||||||
className="w-full border-b border-neutral-300 px-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;
|
resource?: AuditoryResource;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="space-y-4 px-4">
|
||||||
<div className="flex flex-row space-x-4 p-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">
|
<div className="flex w-20 flex-col justify-center space-y-2 sm:w-28">
|
||||||
<SelectImageInput file={resource?.icon} />
|
<SelectImageInput file={resource?.icon} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full overflow-hidden rounded-xl border border-neutral-400 bg-white drop-shadow-lg">
|
<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
|
<InfoInputLine
|
||||||
placeholder="manufacturer"
|
placeholder="manufacturer"
|
||||||
value={resource?.manufacturer?.name ?? ""}
|
value={resource?.manufacturer?.name ?? ""}
|
||||||
|
hint="manufacturer"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<InfoInputLine placeholder="name" value={resource?.name ?? ""} />
|
<InfoInputLine
|
||||||
<span className="my-2 block w-full text-center text-sm italic text-neutral-400">
|
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
|
Edit the fields above
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-4 overflow-hidden rounded-xl border border-neutral-400 bg-neutral-200 text-left shadow"></div>
|
<div>
|
||||||
<div className="ml-4 mr-auto mt-4 rounded-lg border-2 border-neutral-900 bg-neutral-600">
|
<MultiSelector
|
||||||
<span className="px-2 py-2 text-sm text-neutral-200">
|
label="Price Category"
|
||||||
Ages {/** Age range here */}
|
defaultValue={
|
||||||
</span>
|
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>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// const ResourceDescriptionSubForm = ({
|
||||||
|
// resource,
|
||||||
|
// }: {
|
||||||
|
// resource?: AuditoryResource;
|
||||||
|
// }) => {
|
||||||
|
// return <div></div>;
|
||||||
|
// };
|
||||||
|
|
||||||
const ResourceForm = ({ resource }: { resource?: AuditoryResource }) => {
|
const ResourceForm = ({ resource }: { resource?: AuditoryResource }) => {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex max-w-2xl flex-col flex-col-reverse divide-x py-4 sm:flex-row">
|
<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">
|
<div className="my-5 mr-4 flex flex-col justify-end text-lg font-bold">
|
||||||
<ResourceLinkSubForm /> {/** //resource={resource} /> */}
|
<ResourceLinkSubForm /> {/** //resource={resource} /> */}
|
||||||
</div>
|
</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} />
|
<ResourceSummarySubForm resource={resource} />
|
||||||
</div>
|
</div>
|
||||||
</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