From 94dbecb6a3ea4d49e6eb40adc1072d59c45cd086 Mon Sep 17 00:00:00 2001 From: Nate Levison Date: Thu, 19 Feb 2026 12:39:49 -0500 Subject: [PATCH 1/7] basic functionality for questionscale and questionscalemultiple components --- web-app/app/(profile)/questions-test/page.tsx | 83 +++++++++++++++++++ web-app/components/ui/question.tsx | 82 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 web-app/app/(profile)/questions-test/page.tsx create mode 100644 web-app/components/ui/question.tsx diff --git a/web-app/app/(profile)/questions-test/page.tsx b/web-app/app/(profile)/questions-test/page.tsx new file mode 100644 index 0000000..0ba61ab --- /dev/null +++ b/web-app/app/(profile)/questions-test/page.tsx @@ -0,0 +1,83 @@ +import { QuestionScale, QuestionScaleMultiple, QuestionRequiredStar } from "@/components/ui/question"; +import { requireAuth } from "@/lib/auth"; + +export default async function ProfilePage() { + const user = await requireAuth(); + + return ( +
+
+ + = required question + +
+
+

Roommate Habits

+
    +
  1. + + How often are you okay with guests during the day? + +
  2. +
  3. + + How often are you okay with guests spending the night? + +
  4. +
  5. + + How tidy do you want to be? + +
  6. +
  7. + + How comfortable with sharing things (like paper towels) are you? + +
  8. +
  9. + + How often do you need privacy? + +
  10. +
  11. + + How often do you need quiet (like studying or meetings)? + +
  12. +
+
+ +
+

Roommate Demographics

+
    +
  1. + + What year(s) are you okay to room with? + +
  2. +
+
+
+ ); +} diff --git a/web-app/components/ui/question.tsx b/web-app/components/ui/question.tsx new file mode 100644 index 0000000..4b11d4f --- /dev/null +++ b/web-app/components/ui/question.tsx @@ -0,0 +1,82 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function QuestionRequiredStar({ required }: { required: boolean }) { + if (required) { + return ( + * + ) + } + return (); +} + +function QuestionOptionItem({ textLabel, dataName, type }: { textLabel: string, dataName: string, type: string }) { + const id = `${dataName}-radio-${textLabel}`; + return ( +
+ + +
+ ) +} + +type QuestionPropsStandard = { + questionDataName: string, + scaleOptions: string[], + required?: boolean, +} + +function QuestionScale({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & QuestionPropsStandard) { + const items = []; + + for (let i = 0; i < scaleOptions.length; i++) { + items.push( + + ); + } + + return (
+

+ {children} + +

+
+ {items} +
+
); +} + +function QuestionScaleMultiple({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & QuestionPropsStandard) { + const items = []; + + for (let i = 0; i < scaleOptions.length; i++) { + items.push( + + ); + } + + return ( +
+

+ {children} + +

+
+ {items} +
+
+ ) +} + +export { QuestionScale, QuestionScaleMultiple, QuestionRequiredStar }; \ No newline at end of file From d91ded9768e073ff6d276bf6972c28f517fb8263 Mon Sep 17 00:00:00 2001 From: Nate Levison Date: Thu, 19 Feb 2026 12:56:55 -0500 Subject: [PATCH 2/7] fix button alignment --- web-app/app/(profile)/questions-test/page.tsx | 8 ++++++ web-app/components/ui/question.tsx | 28 +++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/web-app/app/(profile)/questions-test/page.tsx b/web-app/app/(profile)/questions-test/page.tsx index 0ba61ab..fdb4903 100644 --- a/web-app/app/(profile)/questions-test/page.tsx +++ b/web-app/app/(profile)/questions-test/page.tsx @@ -62,6 +62,14 @@ export default async function ProfilePage() { How often do you need quiet (like studying or meetings)? +
  • + + When do you wake up? + +
  • diff --git a/web-app/components/ui/question.tsx b/web-app/components/ui/question.tsx index 4b11d4f..bc35d4c 100644 --- a/web-app/components/ui/question.tsx +++ b/web-app/components/ui/question.tsx @@ -14,9 +14,9 @@ function QuestionRequiredStar({ required }: { required: boolean }) { function QuestionOptionItem({ textLabel, dataName, type }: { textLabel: string, dataName: string, type: string }) { const id = `${dataName}-radio-${textLabel}`; return ( -
    +
    - +
    ) } @@ -41,12 +41,12 @@ function QuestionScale({ className, children, questionDataName, scaleOptions, re ); } - return (
    -

    + return (
    +

    {children}

    -
    +
    {items}
    ); @@ -66,17 +66,15 @@ function QuestionScaleMultiple({ className, children, questionDataName, scaleOpt ); } - return ( -
    -

    - {children} - -

    -
    - {items} -
    + return (
    +

    + {children} + +

    +
    + {items}
    - ) +
    ); } export { QuestionScale, QuestionScaleMultiple, QuestionRequiredStar }; \ No newline at end of file From 302b06f4d568063c87bfc7cade286254a90988b4 Mon Sep 17 00:00:00 2001 From: Nate Levison Date: Thu, 19 Feb 2026 13:04:32 -0500 Subject: [PATCH 3/7] question rendering logic into a separate component, spacing changes --- web-app/components/ui/question.tsx | 32 +++++++++++++----------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/web-app/components/ui/question.tsx b/web-app/components/ui/question.tsx index bc35d4c..5ad03d0 100644 --- a/web-app/components/ui/question.tsx +++ b/web-app/components/ui/question.tsx @@ -27,6 +27,18 @@ type QuestionPropsStandard = { required?: boolean, } +function Question({ children, items, required }: React.ComponentProps<"div"> & { items: any[], required: boolean }) { + return (
    +

    + {children} + +

    +
    + {items} +
    +
    ); +} + function QuestionScale({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & QuestionPropsStandard) { const items = []; @@ -41,15 +53,7 @@ function QuestionScale({ className, children, questionDataName, scaleOptions, re ); } - return (
    -

    - {children} - -

    -
    - {items} -
    -
    ); + return ({children}); } function QuestionScaleMultiple({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & QuestionPropsStandard) { @@ -66,15 +70,7 @@ function QuestionScaleMultiple({ className, children, questionDataName, scaleOpt ); } - return (
    -

    - {children} - -

    -
    - {items} -
    -
    ); + return ({children}); } export { QuestionScale, QuestionScaleMultiple, QuestionRequiredStar }; \ No newline at end of file From 55c8278ce2207a7578d19ceda5dbcb5d51ec831d Mon Sep 17 00:00:00 2001 From: Nate Levison Date: Mon, 23 Feb 2026 21:46:20 -0500 Subject: [PATCH 4/7] finish freeform and boolean questions, add cn() to className, --- web-app/app/(profile)/questions-test/page.tsx | 25 +++++- web-app/components/ui/question.tsx | 83 +++++++++++++++---- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/web-app/app/(profile)/questions-test/page.tsx b/web-app/app/(profile)/questions-test/page.tsx index fdb4903..b642de4 100644 --- a/web-app/app/(profile)/questions-test/page.tsx +++ b/web-app/app/(profile)/questions-test/page.tsx @@ -1,7 +1,10 @@ -import { QuestionScale, QuestionScaleMultiple, QuestionRequiredStar } from "@/components/ui/question"; +import { QuestionScale, QuestionScaleMultiple, QuestionRequiredStar, QuestionFreeform, QuestionBoolean } from "@/components/ui/question"; import { requireAuth } from "@/lib/auth"; -export default async function ProfilePage() { +/** + * A testing page for the form components. + */ +export default async function FormTestPage() { const user = await requireAuth(); return ( @@ -84,8 +87,26 @@ export default async function ProfilePage() { What year(s) are you okay to room with? +
  • + + What languages other than English do you speak? + +
  • +
  • + + Are you okay with a smoker? + +
  • +
    +
    +
    +
    ); } diff --git a/web-app/components/ui/question.tsx b/web-app/components/ui/question.tsx index 5ad03d0..7fdacd4 100644 --- a/web-app/components/ui/question.tsx +++ b/web-app/components/ui/question.tsx @@ -1,45 +1,61 @@ import * as React from "react" +import { Input } from "./input"; +import { Label } from "./label"; import { cn } from "@/lib/utils" +import { Textarea } from "./textarea"; +/** + * The red star next to a required question. + * @param required Whether or not to display the star. + */ function QuestionRequiredStar({ required }: { required: boolean }) { if (required) { return ( - * + * ) } return (); } +/** + * Component with an input (like a radio or checkbox) with a label below it. + * The width of the label does not effect the width of the component. + * @param textLabel The label below the option item. + * @param dataName The name in the form of the response. + * @param type The type of input (like "checkbox", "radio"). + */ function QuestionOptionItem({ textLabel, dataName, type }: { textLabel: string, dataName: string, type: string }) { const id = `${dataName}-radio-${textLabel}`; return ( -
    - - +
    + +
    ) } -type QuestionPropsStandard = { - questionDataName: string, - scaleOptions: string[], - required?: boolean, -} - +/** + * Displays the options for a question and aligns them, along with the question text itself. + * @param children The question text component. + * @param items The `QuestionOptionItem`s to select from. + */ function Question({ children, items, required }: React.ComponentProps<"div"> & { items: any[], required: boolean }) { - return (
    -

    + return (
    +

    {children}

    -
    +
    {items}
    ); } -function QuestionScale({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & QuestionPropsStandard) { +/** + * A radio question (select one). + */ +function QuestionScale({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & { questionDataName: string, scaleOptions: string[], required?: boolean}) { const items = []; for (let i = 0; i < scaleOptions.length; i++) { @@ -56,7 +72,10 @@ function QuestionScale({ className, children, questionDataName, scaleOptions, re return ({children}); } -function QuestionScaleMultiple({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & QuestionPropsStandard) { +/** + * A checkbox question (select multiple). + */ +function QuestionScaleMultiple({ className, children, questionDataName, scaleOptions, required=true, ...props }: React.ComponentProps<"div"> & { questionDataName: string, scaleOptions: string[], required?: boolean}) { const items = []; for (let i = 0; i < scaleOptions.length; i++) { @@ -73,4 +92,36 @@ function QuestionScaleMultiple({ className, children, questionDataName, scaleOpt return ({children}); } -export { QuestionScale, QuestionScaleMultiple, QuestionRequiredStar }; \ No newline at end of file +/** + * A freeform textarea question. + */ +function QuestionFreeform({ className, children, questionDataName, required=true, ...props }: React.ComponentProps<"div"> & { questionDataName: string, required?: boolean}) { + return (
    +

    + {children} + +

    +
    +