diff --git a/.changeset/seven-radios-suffer.md b/.changeset/seven-radios-suffer.md new file mode 100644 index 0000000..2f736b0 --- /dev/null +++ b/.changeset/seven-radios-suffer.md @@ -0,0 +1,5 @@ +--- +"namesake": minor +--- + +Display form fields within quest steps diff --git a/convex/questFields.ts b/convex/questFields.ts index 39b807f..576a6cf 100644 --- a/convex/questFields.ts +++ b/convex/questFields.ts @@ -10,6 +10,19 @@ export const getAllFields = query({ }, }); +export const getFields = query({ + args: { fieldIds: v.array(v.id("questFields")) }, + handler: async (ctx, args) => { + const fields = await Promise.all( + args.fieldIds.map(async (fieldId) => { + const field = await ctx.db.get(fieldId); + if (field) return field; + }), + ).then((fields) => fields.filter((field) => field !== undefined)); + return fields; + }, +}); + export const createField = userMutation({ args: { type: field, diff --git a/convex/questSteps.ts b/convex/questSteps.ts index fbc84a7..9face07 100644 --- a/convex/questSteps.ts +++ b/convex/questSteps.ts @@ -10,6 +10,7 @@ export const create = userMutation({ questId: v.id("quests"), title: v.string(), description: v.optional(v.string()), + fields: v.optional(v.array(v.id("questFields"))), }, handler: async (ctx, args) => { const questStepId = await ctx.db.insert("questSteps", { @@ -17,6 +18,7 @@ export const create = userMutation({ title: args.title, description: args.description, creationUser: ctx.userId, + fields: args.fields, }); const quest = await ctx.db.get(args.questId); diff --git a/convex/schema.ts b/convex/schema.ts index b66a471..d7b8f5c 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -31,13 +31,7 @@ const questSteps = defineTable({ creationUser: v.id("users"), title: v.string(), description: v.optional(v.string()), - fields: v.optional( - v.array( - v.object({ - fieldId: v.id("questFields"), - }), - ), - ), + fields: v.optional(v.array(v.id("questFields"))), }).index("questId", ["questId"]); /** @@ -115,7 +109,7 @@ const userQuests = defineTable({ */ const userData = defineTable({ userId: v.id("users"), - fieldId: v.id("fields"), + fieldId: v.id("questFields"), value: v.string(), }).index("userId", ["userId"]); diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index d3cef4a..c9c501d 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -4,7 +4,7 @@ import { Checkbox as AriaCheckbox, CheckboxGroup as AriaCheckboxGroup, type CheckboxGroupProps as AriaCheckboxGroupProps, - type CheckboxProps, + type CheckboxProps as AriaCheckboxProps, type ValidationResult, composeRenderProps, } from "react-aria-components"; @@ -40,7 +40,7 @@ export function CheckboxGroup(props: CheckboxGroupProps) { } const checkboxStyles = tv({ - base: "flex gap-2 items-center group text-sm transition", + base: "flex gap-2 items-center group transition w-max", variants: { isDisabled: { false: "text-gray-normal cursor-pointer", @@ -58,7 +58,7 @@ const boxStyles = tv({ true: "bg-purple-9 dark:bg-purpledark-9 border-transparent", }, isInvalid: { - true: "text-red-9 dark:text-reddark-9 forced-colors:![--color:Mark] group-pressed:[--color:theme(colors.red.800)] dark:group-pressed:[--color:theme(colors.red.700)]", + true: "text-red-9 dark:text-reddark-9 forced-colors:![--color:Mark]", }, isDisabled: { true: "text-gray-7 dark:text-graydark-7 forced-colors:![--color:GrayText]", @@ -69,31 +69,46 @@ const boxStyles = tv({ const iconStyles = "w-5 h-5 text-white group-disabled:text-gray-4 dark:group-disabled:text-gray-9 forced-colors:text-[HighlightText]"; +export interface CheckboxProps extends AriaCheckboxProps { + label?: string; + description?: string; +} + export function Checkbox(props: CheckboxProps) { return ( - - checkboxStyles({ ...renderProps, className }), - )} - > - {({ isSelected, isIndeterminate, ...renderProps }) => ( - <> -
- {isIndeterminate ? ( - - ) : isSelected ? ( - - ) : null} -
- {props.children} - +
+ + checkboxStyles({ ...renderProps, className }), + )} + > + {({ isSelected, isIndeterminate, ...renderProps }) => ( + <> +
+ {isIndeterminate ? ( + + ) : isSelected ? ( + + ) : null} +
+ {props.label} + {props.children} + + )} +
+ {props.description && ( + + {props.description} + )} - +
); } diff --git a/src/components/QuestStep/QuestStep.tsx b/src/components/QuestStep/QuestStep.tsx new file mode 100644 index 0000000..ea9c3ba --- /dev/null +++ b/src/components/QuestStep/QuestStep.tsx @@ -0,0 +1,88 @@ +import { api } from "@convex/_generated/api"; +import type { Doc, Id } from "@convex/_generated/dataModel"; +import { useQuery } from "convex/react"; +import Markdown from "react-markdown"; +import { Card } from "../Card"; +import { Checkbox } from "../Checkbox"; +import { DateField } from "../DateField"; +import { NumberField } from "../NumberField"; +import { Select, SelectItem } from "../Select"; +import { TextArea } from "../TextArea"; +import { TextField } from "../TextField"; + +export interface QuestStepProps { + title: string; + description?: string; + fields?: Id<"questFields">[]; +} + +export function QuestFields(props: { questFields: Doc<"questFields">[] }) { + const markupForField = (field: Doc<"questFields">) => { + switch (field.type) { + case "text": + return ( + + ); + case "email": + return ( + + ); + case "phone": + return ( + + ); + case "textarea": + return