Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion actions/supabase/queries/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PromptType, UUID } from "@/types/schema";
import supabase from "../client";

export async function getOptionsForPrompt(prompt_id: UUID) {
export async function getOptionsForPrompt(prompt_id: string) {
const { data, error } = await supabase
.from("prompt_option")
.select("*")
Expand Down
95 changes: 74 additions & 21 deletions actions/supabase/queries/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,39 +333,92 @@ export async function fetchRole(
return data.role_id;
}

export async function createPromptAnswer(
export async function deletePromptAnswers(
userId: string,
promptId: string,
sessionId: UUID,
phaseId: UUID,
answer: string,
) {
const supabase = await getSupabaseServerClient();
const { data, error } = await supabase
const { error } = await supabase
.from("prompt_response")
.upsert(
[
{
prompt_response_id: crypto.randomUUID(),
session_id: sessionId,
phase_id: phaseId,
user_id: userId,
prompt_id: promptId,
prompt_answer: answer,
},
],
{ onConflict: "user_id,prompt_id,session_id" },
)
.select("prompt_response_id");
.delete()
.match({ user_id: userId, prompt_id: promptId, session_id: sessionId });

if (error) {
console.error(
"Error creating prompt answer:",
"Error deleting prompt answers:",
JSON.stringify(error, null, 2),
);
} else {
console.log("Insert success:", data);
}
}

export async function createPromptAnswer(
userId: string,
promptId: string,
sessionId: UUID,
phaseId: UUID,
answer: string,
promptType: string | null,
) {
const supabase = await getSupabaseServerClient();

const isOptionPrompt =
promptType === "checkbox" || promptType === "multiple_choice";

const baseRow = {
session_id: sessionId,
phase_id: phaseId,
user_id: userId,
prompt_id: promptId,
};

// OPTION PROMPTS (checkbox / MCQ)
if (isOptionPrompt) {
const { data, error } = await supabase
.from("prompt_response")
.insert({
...baseRow,
prompt_response_id: crypto.randomUUID(),
prompt_option_id: answer,
prompt_answer: null,
})
.select("prompt_response_id");

if (error) console.error("Error inserting option response:", error);
return data;
}

// TEXT PROMPTS (single response)
const { data: updated, error: updateError } = await supabase
.from("prompt_response")
.update({
prompt_answer: answer,
prompt_option_id: null,
})
.match({
user_id: userId,
prompt_id: promptId,
session_id: sessionId,
})
.select("prompt_response_id");

if (updateError) console.error("Update error:", updateError);

if (updated && updated.length > 0) return updated;

// Insert if no existing row
const { data, error } = await supabase
.from("prompt_response")
.insert({
...baseRow,
prompt_response_id: crypto.randomUUID(),
prompt_answer: answer,
prompt_option_id: null,
})
.select("prompt_response_id");

if (error) console.error("Insert error:", error);

return data;
}

Expand Down
60 changes: 60 additions & 0 deletions app/participants/components/CheckboxPromptParticipant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Checkbox, FormControlLabel } from "@mui/material";
import { PromptOption } from "@/types/schema";
import {
CheckboxOptionParticipantStyled,
CheckboxOptionTextStyled,
CheckboxParticipantStyled,
} from "./styles";

type Props = {
options: PromptOption[];
values: string[];
onChange: (value: string[]) => void;
};

export default function CheckboxPromptParticipant({
options,
values,
onChange,
}: Props) {
function toggle(id: string) {
const set = new Set(values);

if (set.has(id)) {
set.delete(id);
} else {
set.add(id);
}

onChange(Array.from(set));
}

return (
<CheckboxParticipantStyled>
{options.map(o => {
const selected = values.includes(o.option_id);
return (
<CheckboxOptionParticipantStyled
key={o.option_id}
$selected={selected}
>
<FormControlLabel
control={
<Checkbox
size="small"
checked={selected}
onChange={() => toggle(o.option_id)}
/>
}
label={
<CheckboxOptionTextStyled $selected={selected}>
{o.option_text}
</CheckboxOptionTextStyled>
}
/>
</CheckboxOptionParticipantStyled>
);
})}
</CheckboxParticipantStyled>
);
}
47 changes: 47 additions & 0 deletions app/participants/components/MultipleChoicePromptParticipant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { FormControlLabel, Radio, RadioGroup } from "@mui/material";
import { PromptOption } from "@/types/schema";
import {
McqOptionParticipantStyled,
McqOptionTextStyled,
MultipleChoiceParticipantStyled,
} from "./styles";

type Props = {
options: PromptOption[];
value: string;
onChange: (value: string) => void;
};

export default function MultipleChoicePromptParticipant({
options,
value,
onChange,
}: Props) {
return (
<MultipleChoiceParticipantStyled>
<RadioGroup
value={value}
onChange={e => onChange(e.target.value)}
name="mcq-participant"
>
{options.map(o => (
<McqOptionParticipantStyled
key={o.option_id}
$selected={value === o.option_id}
>
<FormControlLabel
value={o.option_id}
control={<Radio size="small" />}
label={
<McqOptionTextStyled $selected={value === o.option_id}>
{" "}
{o.option_text}
</McqOptionTextStyled>
}
/>
</McqOptionParticipantStyled>
))}
</RadioGroup>
</MultipleChoiceParticipantStyled>
);
}
17 changes: 10 additions & 7 deletions app/participants/components/ParticipantNextButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { UUID } from "@/types/schema";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { setIsFinished } from "@/actions/supabase/queries/sessions";
import { NextButtonContainerStyled } from "../session-flow/styles";
import { Button } from "../styles";

interface NextButtonProps {
Expand Down Expand Up @@ -49,12 +50,14 @@ export default function NextButton({
}

return (
<div>
<Button onClick={handleClick} disabled={clicked}>
{isLastPhase ? "Finish Game" : "Next"}
</Button>

{clicked && <span> waiting for others...</span>}
</div>
<NextButtonContainerStyled>
{clicked ? (
<span> waiting for others...</span>
) : (
<Button onClick={handleClick} disabled={clicked}>
{isLastPhase ? "Finish Game" : "Next"}
</Button>
)}
</NextButtonContainerStyled>
);
}
78 changes: 78 additions & 0 deletions app/participants/components/PromptRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use client";

import CheckboxPromptParticipant from "@/app/participants/components/CheckboxPromptParticipant";
import MultipleChoicePromptParticipant from "@/app/participants/components/MultipleChoicePromptParticipant";
import TextPromptParticipant from "@/app/participants/components/TextPromptParticipant";
import { PromptWithOption } from "@/app/participants/session-flow/page";
import {
PromptQuestionArrowStyled,
PromptQuestionContentStyled,
PromptQuestionContentTitledStyled,
PromptQuestionStyled,
} from "../session-flow/styles";

type PromptRendererProps = {
index: number;
promptWithOption: PromptWithOption;
answer: string | string[];
onAnswer: (value: string | string[]) => void;
onBlur: (value: string | string[]) => void;
};

export default function PromptRenderer({
index,
promptWithOption,
answer,
onAnswer,
onBlur,
}: PromptRendererProps) {
const { prompt, options } = promptWithOption;

function handleChange(value: string | string[]) {
onAnswer(value);
// fire immediately for selection types
if (prompt.prompt_type !== "text") {
onBlur(value);
}
}

const arrowString: string = "->";

return (
<PromptQuestionStyled>
<PromptQuestionArrowStyled>
{index + 1} {arrowString}{" "}
</PromptQuestionArrowStyled>

<PromptQuestionContentStyled>
<PromptQuestionContentTitledStyled>
{prompt.prompt_text}
</PromptQuestionContentTitledStyled>

{prompt.prompt_type === "text" && (
<TextPromptParticipant
value={(answer as string) || ""}
onChange={onAnswer}
onBlur={() => onBlur(answer)}
/>
)}

{prompt.prompt_type === "multiple_choice" && (
<MultipleChoicePromptParticipant
options={options}
value={(answer as string) || ""}
onChange={handleChange}
/>
)}

{prompt.prompt_type === "checkbox" && (
<CheckboxPromptParticipant
options={options}
values={(answer as string[]) || []}
onChange={handleChange}
/>
)}
</PromptQuestionContentStyled>
</PromptQuestionStyled>
);
}
22 changes: 22 additions & 0 deletions app/participants/components/TextPromptParticipant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { TextFieldParticpantsStyled } from "./styles";

type TextPromptParticipantProps = {
value: string;
onChange: (value: string) => void;
onBlur?: () => void;
};

export default function TextPromptParticipant({
value,
onChange,
onBlur,
}: TextPromptParticipantProps) {
return (
<TextFieldParticpantsStyled
value={value}
placeholder="Type your answer..."
onChange={e => onChange(e.target.value)}
onBlur={onBlur}
/>
);
}
Loading
Loading