From f265c1642db03a025f9ba9f4d86c1ef37ba2c7a8 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 11 Jun 2025 15:29:11 +0700 Subject: [PATCH 01/16] setup generic --- public/index.js | 58 +++++++++++++++++-- .../container/hooks/use-base-effect.js | 5 +- src/components/container/hooks/use-props.js | 6 +- src/components/index.js | 2 + src/components/status/assessment.js | 1 + src/components/survey/index.js | 2 + src/lib/recoil/assessment.js | 37 +++++++++++- src/lib/recoil/base.js | 1 + src/lib/traitify.js | 3 +- 9 files changed, 106 insertions(+), 9 deletions(-) diff --git a/public/index.js b/public/index.js index 65b00a56..153c4c39 100644 --- a/public/index.js +++ b/public/index.js @@ -63,6 +63,11 @@ function createWidget() { if(!orderID) { return; } Traitify.listener.on("Survey.start", (x) => console.log(x)); Traitify.options.orderID = orderID; + } else if(surveyType === "generic") { + Traitify.options.surveyID = cache.get("surveyID"); + Traitify.options.profileID = cache.get("profileID"); + Traitify.options.assessmentID = cache.get("assessmentID"); + Traitify.options.surveyType = surveyType; } else { const assessmentID = cache.get("assessmentID"); console.log("createWidget", {assessmentID}); @@ -144,6 +149,7 @@ function createAssessment() { if(cache.get("surveyType") === "benchmark") { return createWidget(); } if(cache.get("surveyType") === "cognitive") { return createCognitiveAssessment(); } if(cache.get("surveyType") === "order") { return createWidget(); } + if(cache.get("surveyType") === "generic") { return createGenericAssessment(); } const params = { deck_id: cache.get("deckID"), @@ -240,6 +246,24 @@ function createElement(options = {}) { return element; } +function createGenericAssessment() { + Traitify.http.authKey = "admin-secret"; + const query = Traitify.GraphQL.generic.create; + const variables = { + surveyID: cache.get("surveyID"), + profileID: cache.get("profileID") + }; + + Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { + try { + const id = response.data.getOrCreateGenericAssessment.id; + } catch(error) { + console.log(error); + } + setTimeout(createWidget, 500); + }); +} + function destroyWidget() { Traitify.destroy(); } function setupTargets() { @@ -370,7 +394,8 @@ function setupDom() { {text: "Benchmark", value: "benchmark"}, {text: "Cognitive", value: "cognitive"}, {text: "Order", value: "order"}, - {text: "Personality", value: "personality"} + {text: "Personality", value: "personality"}, + {text: "Generic", value: "generic"} ], text: "Survey Type:" })); @@ -404,6 +429,9 @@ function setupDom() { row.appendChild(createOption({name: "orderID", text: "Order ID:"})); group.appendChild(row) + row = createElement({className: surveyType !== "generic" ? "hide" : "", id: "generic-options"}); + row.appendChild(createOption({name: "profileID", text: "Profile ID:"})); + group.appendChild(row); row = createElement({className: "row"}); row.appendChild(createElement({onClick: createAssessment, tag: "button", text: "Create / Load"})); group.appendChild(row); @@ -427,11 +455,32 @@ function setupCognitive() { }); } +function setupGeneric() { + Traitify.http.authKey = "admin-secret"; + const query = Traitify.GraphQL.generic.surveys; + const variables = {localeKey: cache.get("locale")}; + + Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { + const options = response.data.genericAssessments + .map(({id, name}) => ({text: name, value: id})) + .sort((a, b) => a.text.localeCompare(b.text)); + + document.querySelector("#generic-options").appendChild(createOption({ + name: "surveyID", + onChange: onInputChange, + options, + text: "Survey:" + })); + }); + +} + function setupTraitify() { const environment = cache.get("environment"); if(environment === "staging") { - Traitify.http.host = "https://api.stag.awse.traitify.com"; + // Traitify.http.host = "https://api.stag.awse.traitify.com"; + Traitify.http.host = "http://localhost:4000/"; } else { Traitify.http.host = "https://api.traitify.com"; } @@ -455,7 +504,7 @@ function onSurveyTypeChange(e) { const name = e.target.name; const value = e.target.value; const assessmentID = cache.get(`${value}AssessmentID`); - const otherValues = ["benchmark", "cognitive", "order", "personality"].filter((type) => type !== value); + const otherValues = ["benchmark", "cognitive", "order", "personality", "generic"].filter((type) => type !== value); cache.set("assessmentID", assessmentID); @@ -467,5 +516,6 @@ function onSurveyTypeChange(e) { setupTraitify(); setupDom(); -setupCognitive(); +// setupCognitive(); +setupGeneric(); createWidget(); diff --git a/src/components/container/hooks/use-base-effect.js b/src/components/container/hooks/use-base-effect.js index 8c0b69f5..364cf648 100644 --- a/src/components/container/hooks/use-base-effect.js +++ b/src/components/container/hooks/use-base-effect.js @@ -7,7 +7,8 @@ import { orderIDState, orderState, packageIDState, - profileIDState + profileIDState, + surveyIDState } from "lib/recoil"; export default function useBaseEffect() { @@ -17,10 +18,12 @@ export default function useBaseEffect() { const setOrderID = useSetRecoilState(orderIDState); const setPackageID = useSetRecoilState(packageIDState); const setProfileID = useSetRecoilState(profileIDState); + const setSurveyID = useSetRecoilState(surveyIDState); useEffect(() => { setBenchmarkID(base.benchmarkID); }, [base.benchmarkID]); useEffect(() => { setOrderID(base.orderID); }, [base.orderID]); useEffect(() => { setPackageID(base.packageID); }, [base.packageID]); useEffect(() => { setProfileID(base.profileID); }, [base.profileID]); + useEffect(() => { setSurveyID(base.surveyID); }, [base.surveyID]); useDidUpdate(() => { resetOrder(); }, [base]); } diff --git a/src/components/container/hooks/use-props.js b/src/components/container/hooks/use-props.js index 74914c34..2a33e911 100644 --- a/src/components/container/hooks/use-props.js +++ b/src/components/container/hooks/use-props.js @@ -33,7 +33,8 @@ export default function useProps(props) { options = {}, orderID, packageID, - profileID + profileID, + surveyID } = props; useEffect(() => { @@ -74,7 +75,8 @@ export default function useProps(props) { if(orderID) { base.orderID = orderID; } if(packageID) { base.packageID = packageID; } if(profileID) { base.profileID = profileID; } + if(surveyID) { base.surveyID = surveyID; } setBase(base); - }, [assessmentID, benchmarkID, orderID, packageID, profileID]); + }, [assessmentID, benchmarkID, orderID, packageID, profileID, surveyID]); } diff --git a/src/components/index.js b/src/components/index.js index ec0d2e6b..3de77d8d 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -29,6 +29,7 @@ import RecommendationChart from "./results/recommendation/chart"; import Status from "./status"; import Survey from "./survey"; import CognitiveSurvey from "./survey/cognitive"; +import Generic from "./survey/generic"; import PersonalitySurvey from "./survey/personality"; export default { @@ -86,6 +87,7 @@ export default { Survey: { Cognitive: CognitiveSurvey, Container: Survey, + Generic, Personality: PersonalitySurvey } }; diff --git a/src/components/status/assessment.js b/src/components/status/assessment.js index a9cc3224..0ce1a321 100644 --- a/src/components/status/assessment.js +++ b/src/components/status/assessment.js @@ -18,6 +18,7 @@ const translations = { survey: { cognitive_assessment: "Cognitive Assessment", external_assessment: "External Assessment", + generic_assessment: "Generic Assessment", personality_assessment: "Personality Assessment" } }; diff --git a/src/components/survey/index.js b/src/components/survey/index.js index 16d3361a..a84f7f65 100644 --- a/src/components/survey/index.js +++ b/src/components/survey/index.js @@ -1,6 +1,7 @@ import Status from "components/status"; import useActive from "lib/hooks/use-active"; import Cognitive from "./cognitive"; +import Generic from "./generic"; import Personality from "./personality"; export default function Survey() { @@ -10,6 +11,7 @@ export default function Survey() { if(active.surveyType === "cognitive") { return ; } if(active.surveyType === "external") { return ; } if(active.surveyType === "personality") { return ; } + if(active.surveyType === "generic") { return ; } return null; } diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 17dec745..60bea727 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -6,7 +6,9 @@ import { graphqlState, httpState, localeState, - safeCacheKeyState + profileIDState, + safeCacheKeyState, + surveyIDState } from "./base"; // TODO: Return error instead of null for cognitive and external @@ -102,11 +104,44 @@ export const personalityAssessmentQuery = selectorFamily({ key: "assessment/personality" }); +export const genericAssessmentQuery = selectorFamily({ + get: (id) => async({get}) => { + if(!id) { return null; } + + const cache = get(cacheState); + const cacheKey = get(safeCacheKeyState({id, type: "assessment"})); + const cached = cache.get(cacheKey); + if(cached) { return cached; } + + const GraphQL = get(graphqlState); + const http = get(httpState); + const params = { + query: GraphQL.generic.questions, + variables: {profileID: get(profileIDState), surveyID: get(surveyIDState)} + }; + + const response = await http.post({path: GraphQL.generic.path, params}); + if(response.errors) { + console.warn("generic-assessment", response.errors); /* eslint-disable-line no-console */ + return null; + } + + const questions = response.data.genericAssessmentQuestions; + if(!questions?.length) { return questions; } + + cache.set(cacheKey, questions); + + return questions; + }, + key: "assessment/generic" +}); + export const assessmentQuery = selectorFamily({ get: ({id, surveyType}) => async({get}) => { if(surveyType === "cognitive") { return get(cognitiveAssessmentQuery(id)); } if(surveyType === "external") { return get(externalAssessmentQuery(id)); } if(surveyType === "personality") { return get(personalityAssessmentQuery(id)); } + if(surveyType === "generic") { return get(genericAssessmentQuery(id)); } return null; }, diff --git a/src/lib/recoil/base.js b/src/lib/recoil/base.js index 108a4b89..e86075ba 100644 --- a/src/lib/recoil/base.js +++ b/src/lib/recoil/base.js @@ -24,6 +24,7 @@ export const optionsState = atom({default: null, key: "options"}); export const orderIDState = atom({default: null, key: "order-id"}); export const packageIDState = atom({default: null, key: "package-id"}); export const profileIDState = atom({default: null, key: "profile-id"}); +export const surveyIDState = atom({default: null, key: "survey-id"}); // NOTE: Breaking up state prevents over-triggering selectors export const activeIDState = selector({ diff --git a/src/lib/traitify.js b/src/lib/traitify.js index 2c5db73e..59004a19 100644 --- a/src/lib/traitify.js +++ b/src/lib/traitify.js @@ -29,7 +29,8 @@ export default class Traitify { "locale", "orderID", "packageID", - "profileID" + "profileID", + "surveyID" ]); return {...objects, ...props, options}; From 48dc4be560bc63efad4daadee2370de4e9589e6d Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Wed, 11 Jun 2025 15:29:43 +0700 Subject: [PATCH 02/16] add generic component graphql --- src/components/survey/generic/index.js | 9 +++++ src/lib/graphql/generic.js | 46 ++++++++++++++++++++++++++ src/lib/graphql/index.js | 2 ++ 3 files changed, 57 insertions(+) create mode 100644 src/components/survey/generic/index.js create mode 100644 src/lib/graphql/generic.js diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js new file mode 100644 index 00000000..ab8c2078 --- /dev/null +++ b/src/components/survey/generic/index.js @@ -0,0 +1,9 @@ +import {useEffect, useState} from "react"; +import useAssessment from "lib/hooks/use-assessment"; + +export default function Generic() { + const assessment = useAssessment({surveyType: "generic"}); + return ( +

Generic

+ ); +} diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js new file mode 100644 index 00000000..f60bc483 --- /dev/null +++ b/src/lib/graphql/generic.js @@ -0,0 +1,46 @@ +export const surveys = ` + query($localeKey: String!) { + genericAssessments(localeKey: $localeKey) { + id + name + } + } +`; + +export const create = ` + mutation($profileID: ID!, $surveyID: ID!) { + getOrCreateGenericAssessment(profileId: $profileID, surveyId: $surveyID) { + id + surveyId + profileId + startedAt + completedAt + } + } +`; + +export const questions = ` + query($profileID: ID!, $surveyID: ID!) { + genericAssessmentQuestions(profileId: $profileID, surveyId: $surveyID) { + id + name + conclusions + instructions + instructionButton + questionSets { + text + setImage + questions { + id + text + responseOptions { + id + text + } + } + } + } + } +`; + +export const path = "/generic-assessments/graphql"; diff --git a/src/lib/graphql/index.js b/src/lib/graphql/index.js index 57df33d2..357217c4 100644 --- a/src/lib/graphql/index.js +++ b/src/lib/graphql/index.js @@ -1,6 +1,7 @@ import * as benchmark from "./benchmark"; import * as cognitive from "./cognitive"; import * as external from "./external"; +import * as generic from "./generic"; import * as guide from "./guide"; import * as order from "./order"; import * as xavier from "./xavier"; @@ -9,6 +10,7 @@ export default { benchmark, cognitive, external, + generic, guide, order, xavier From ddad0dfde5d087dc9176bd2a49aa9d6d4a7e82c8 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 13 Jun 2025 17:50:10 +0700 Subject: [PATCH 03/16] render questions --- public/index.js | 25 +++-- src/components/survey/generic/container.js | 18 ++++ src/components/survey/generic/index.js | 21 +++- src/components/survey/generic/question-set.js | 29 ++++++ src/components/survey/generic/responses.js | 29 ++++++ src/components/survey/generic/style.scss | 98 +++++++++++++++++++ 6 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 src/components/survey/generic/container.js create mode 100644 src/components/survey/generic/question-set.js create mode 100644 src/components/survey/generic/responses.js create mode 100644 src/components/survey/generic/style.scss diff --git a/public/index.js b/public/index.js index 153c4c39..93975959 100644 --- a/public/index.js +++ b/public/index.js @@ -257,6 +257,7 @@ function createGenericAssessment() { Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { try { const id = response.data.getOrCreateGenericAssessment.id; + cache.set("assessmentID", id); } catch(error) { console.log(error); } @@ -461,16 +462,20 @@ function setupGeneric() { const variables = {localeKey: cache.get("locale")}; Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { - const options = response.data.genericAssessments - .map(({id, name}) => ({text: name, value: id})) - .sort((a, b) => a.text.localeCompare(b.text)); - - document.querySelector("#generic-options").appendChild(createOption({ - name: "surveyID", - onChange: onInputChange, - options, - text: "Survey:" - })); + try { + const options = response.data.genericAssessments + .map(({id, name}) => ({text: name, value: id})) + .sort((a, b) => a.text.localeCompare(b.text)); + + document.querySelector("#generic-options").appendChild(createOption({ + name: "surveyID", + onChange: onInputChange, + options, + text: "Survey:" + })); + } catch(error) { + console.log(error); + } }); } diff --git a/src/components/survey/generic/container.js b/src/components/survey/generic/container.js new file mode 100644 index 00000000..670c360b --- /dev/null +++ b/src/components/survey/generic/container.js @@ -0,0 +1,18 @@ +import PropTypes from "prop-types"; +import style from "./style.scss"; + +export default function Container({children, progress}) { + return ( +
+
+
+
+ {children} +
+ ); +} + +Container.propTypes = { + children: PropTypes.node.isRequired, + progress: PropTypes.number.isRequired +}; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index ab8c2078..1c714f90 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -1,9 +1,26 @@ -import {useEffect, useState} from "react"; +import {useState} from "react"; import useAssessment from "lib/hooks/use-assessment"; +import Container from "./container"; +import QuestionSet from "./question-set"; export default function Generic() { + const [questionSetIndex, setQuestionSetIndex] = useState(0); const assessment = useAssessment({surveyType: "generic"}); + const questionSets = assessment ? assessment.questionSets : []; + const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; + const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; + const lastSlide = questionSets[questionSetIndex - 1]; + const nextSlide = questionSets[questionSetIndex + 1]; + + const props = {progress}; + + const updateSlide = (response) => { + console.log("Updating slide with response:", response); + }; + return ( -

Generic

+ + {currentQuestionSet && } + ); } diff --git a/src/components/survey/generic/question-set.js b/src/components/survey/generic/question-set.js new file mode 100644 index 00000000..a197ced2 --- /dev/null +++ b/src/components/survey/generic/question-set.js @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; +import Responses from "./responses"; +import style from "./style.scss"; + +export default function QuestionSet({text, questions = [], setImage}) { + const questionSetClass = [style.questionSet].join(" "); + + return ( +
+ {text} +
+ {questions.map((question) => ( +
+

{question.text}

+ +
+ ))} +
+ ); +} + +QuestionSet.propTypes = { + text: PropTypes.string.isRequired, + questions: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired + })).isRequired, + setImage: PropTypes.string.isRequired +}; diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js new file mode 100644 index 00000000..ee16e5d2 --- /dev/null +++ b/src/components/survey/generic/responses.js @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; +import style from "./style.scss"; + +export default function Responses({responseOptions = []}) { + const buttonClass = ["traitify--response-button", style.response].join(" "); + const buttonWidth = (text) => { + console.log("Calculating button width for text:", text.length); + return text.length > 20 ? "100%" : "auto"; + }; + + return ( +
+ {responseOptions.map((option) => ( + + ))} +
+ ); +} + +Responses.propTypes = { + responseOptions: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired + }) + ).isRequired +}; diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss new file mode 100644 index 00000000..4b15fe58 --- /dev/null +++ b/src/components/survey/generic/style.scss @@ -0,0 +1,98 @@ +@import "style/helpers"; + +.container { + @extend %container; + display: flex; + flex-direction: column; + margin: 0 auto; + max-height: 100vh; + max-height: 100svh; + max-width: 100%; + position: relative; + z-index: 1; + padding: $buffer-lg; + + @include min-width("xs") { + aspect-ratio: 2/1; + max-width: $breakpoint-md * 0.85; + } + + button { + border: 1px solid #DADCE0; + border-radius: $border-radius-sm * 0.5; + margin: $buffer $buffer * 0.5; + height: 48px; + } + + .progress { + border-radius: $border-radius-sm; + height: 100%; + transition: width 0.3s; + width: 0%; + + @include theme("background-color", "progress-bar"); + } + .progressBar { + border-radius: $border-radius-sm; + height: $border-width-xl; + margin-bottom: $buffer * 2; + + @include theme("background-color", "border"); + } + &:after { + top: 0; + bottom: 0; + border: $border-width solid; + border-radius: $border-radius; + content: ""; + left: 0; + position: absolute; + right: 0; + z-index: -1; + + @include theme("border-color", "border"); + } +} + +.questionSet { + width: 100%; + align-items: center; + text-align: center; + padding: $buffer; + + img { + margin-bottom: $buffer * 2; + } + + .question { + text-align: left; + } +} + +.response { + flex: 1 1 0; + letter-spacing: 2px; + line-height: 2; + padding: $buffer * 0.25 $buffer * 0.75; + text-align: center; + + @include min-width("xs") { padding: $buffer * 0.25 $buffer * 0.75; } + @include min-width("sm") { + font-size: $font-size * 1.25; + line-height: $line-height; + padding: $buffer * 0.5 $buffer * 0.75; + } + @include theme("color", "text-light"); + &:focus { + outline-offset: -1px; + outline-style: solid; + outline-width: 1px; + } + button.btnActive { + background-color: #25C9D0; + color: #FFFFFF; + } + button.btnDisabled { + &:active, &:hover { @include theme("background-color", "inactive"); } + } +} \ No newline at end of file From 07d1ad524497c508e7570e09f4cb9353c997c9f3 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Sun, 15 Jun 2025 22:14:14 +0700 Subject: [PATCH 04/16] render questions and collect answers --- src/components/survey/generic/index.js | 47 ++++++++++++++++--- src/components/survey/generic/question-set.js | 26 ++++++---- src/components/survey/generic/responses.js | 14 ++++-- src/components/survey/generic/style.scss | 27 +++++++++-- 4 files changed, 91 insertions(+), 23 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 1c714f90..7d1b8e2a 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -1,26 +1,61 @@ -import {useState} from "react"; +import {faChevronLeft} from "@fortawesome/free-solid-svg-icons"; +import {useEffect, useState} from "react"; +import Icon from "components/common/icon"; import useAssessment from "lib/hooks/use-assessment"; +import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; import QuestionSet from "./question-set"; +import style from "./style.scss"; export default function Generic() { const [questionSetIndex, setQuestionSetIndex] = useState(0); + const [answers, setAnswers] = useState([]); const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; - const lastSlide = questionSets[questionSetIndex - 1]; - const nextSlide = questionSets[questionSetIndex + 1]; + const finished = questionSets.length > 0 && questionSets.length === answers.length; const props = {progress}; + const translate = useTranslate(); - const updateSlide = (response) => { - console.log("Updating slide with response:", response); + const updateSlide = (questionId, selectedOptionId) => { + const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); + setAnswers([...currentAnswers, + {questionId, selectedResponseOptionId: selectedOptionId}]); + if(questionSetIndex + 1 < questionSets.length) { + setQuestionSetIndex(questionSetIndex + 1); + } }; + const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; + + const onSubmit = () => { + console.log("Submitting answers:", answers); + }; + + useEffect(() => { + if(!finished) { return; } + onSubmit(); + }, [finished]); + return ( - {currentQuestionSet && } + {currentQuestionSet + && ( + + )} + + {questionSetIndex > 0 && ( + + )} ); } diff --git a/src/components/survey/generic/question-set.js b/src/components/survey/generic/question-set.js index a197ced2..c50e4415 100644 --- a/src/components/survey/generic/question-set.js +++ b/src/components/survey/generic/question-set.js @@ -2,17 +2,20 @@ import PropTypes from "prop-types"; import Responses from "./responses"; import style from "./style.scss"; -export default function QuestionSet({text, questions = [], setImage}) { +export default function QuestionSet({questionSet, updateSlide = null}) { const questionSetClass = [style.questionSet].join(" "); return (
- {text} + {questionSet.text}
- {questions.map((question) => ( + {questionSet.questions.map((question) => (

{question.text}

- + updateSlide(question.id, optionId)} + />
))}
@@ -20,10 +23,13 @@ export default function QuestionSet({text, questions = [], setImage}) { } QuestionSet.propTypes = { - text: PropTypes.string.isRequired, - questions: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - text: PropTypes.string.isRequired - })).isRequired, - setImage: PropTypes.string.isRequired + questionSet: PropTypes.shape({ + text: PropTypes.string.isRequired, + questions: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired + })).isRequired, + setImage: PropTypes.string.isRequired + }).isRequired, + updateSlide: PropTypes.func }; diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js index ee16e5d2..4aa470b0 100644 --- a/src/components/survey/generic/responses.js +++ b/src/components/survey/generic/responses.js @@ -1,17 +1,22 @@ import PropTypes from "prop-types"; import style from "./style.scss"; -export default function Responses({responseOptions = []}) { +export default function Responses({responseOptions = [], updateSlide = null}) { const buttonClass = ["traitify--response-button", style.response].join(" "); const buttonWidth = (text) => { - console.log("Calculating button width for text:", text.length); return text.length > 20 ? "100%" : "auto"; }; return (
{responseOptions.map((option) => ( - ))} @@ -25,5 +30,6 @@ Responses.propTypes = { id: PropTypes.string.isRequired, text: PropTypes.string.isRequired }) - ).isRequired + ).isRequired, + updateSlide: PropTypes.func }; diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 4b15fe58..2011ea4f 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -5,8 +5,6 @@ display: flex; flex-direction: column; margin: 0 auto; - max-height: 100vh; - max-height: 100svh; max-width: 100%; position: relative; z-index: 1; @@ -22,6 +20,27 @@ border-radius: $border-radius-sm * 0.5; margin: $buffer $buffer * 0.5; height: 48px; + + .back { + border-radius: $border-radius-sm; + bottom: 30px; + opacity: 0.7; + padding: $buffer * 0.5; + position: absolute; + width: 40px; + z-index: 1; + + @include theme("color", "text-light"); + @include theme("background-color", "fullscreen"); + &:hover{ opacity: 1; } + svg { + display: block; + height: 100%; + margin: auto; + width: auto; + } + } + .back { left: 30px; } } .progress { @@ -95,4 +114,6 @@ button.btnDisabled { &:active, &:hover { @include theme("background-color", "inactive"); } } -} \ No newline at end of file +} + +.hide { display: none; } \ No newline at end of file From d065fc44a0f0bb892ce61c8c21db64434e49f5cc Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Sun, 15 Jun 2025 22:18:06 +0700 Subject: [PATCH 05/16] refactor --- src/components/survey/generic/index.js | 1 - src/components/survey/generic/responses.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 7d1b8e2a..bf4fcef0 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -5,7 +5,6 @@ import useAssessment from "lib/hooks/use-assessment"; import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; import QuestionSet from "./question-set"; -import style from "./style.scss"; export default function Generic() { const [questionSetIndex, setQuestionSetIndex] = useState(0); diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js index 4aa470b0..2807914d 100644 --- a/src/components/survey/generic/responses.js +++ b/src/components/survey/generic/responses.js @@ -3,9 +3,7 @@ import style from "./style.scss"; export default function Responses({responseOptions = [], updateSlide = null}) { const buttonClass = ["traitify--response-button", style.response].join(" "); - const buttonWidth = (text) => { - return text.length > 20 ? "100%" : "auto"; - }; + const buttonWidth = (text) => (text.length > 20 ? "100%" : "auto"); return (
From 662aecb61a20b929888cca386bbdb9a34988036d Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Mon, 16 Jun 2025 23:42:50 +0700 Subject: [PATCH 06/16] handle multi questions set --- src/components/survey/generic/index.js | 12 ++++++------ src/components/survey/generic/question-set.js | 19 ++++++++++++++++--- src/components/survey/generic/responses.js | 14 ++++++++++---- src/components/survey/generic/style.scss | 5 +++-- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index bf4fcef0..c7351836 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -11,22 +11,21 @@ export default function Generic() { const [answers, setAnswers] = useState([]); const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; + const questionCount = questionSets.reduce((count, questionSet) => count + questionSet.questions.length, 0); const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; - const finished = questionSets.length > 0 && questionSets.length === answers.length; + const finished = questionSets.length > 0 && questionCount === answers.length; const props = {progress}; const translate = useTranslate(); - const updateSlide = (questionId, selectedOptionId) => { + const updateAnswer = (questionId, selectedOptionId) => { const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); setAnswers([...currentAnswers, {questionId, selectedResponseOptionId: selectedOptionId}]); - if(questionSetIndex + 1 < questionSets.length) { - setQuestionSetIndex(questionSetIndex + 1); - } }; + const next = () => { setQuestionSetIndex(questionSetIndex + 1); }; const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; const onSubmit = () => { @@ -45,7 +44,8 @@ export default function Generic() { )} diff --git a/src/components/survey/generic/question-set.js b/src/components/survey/generic/question-set.js index c50e4415..5ed9060c 100644 --- a/src/components/survey/generic/question-set.js +++ b/src/components/survey/generic/question-set.js @@ -1,9 +1,21 @@ import PropTypes from "prop-types"; +import {useEffect, useState} from "react"; import Responses from "./responses"; import style from "./style.scss"; -export default function QuestionSet({questionSet, updateSlide = null}) { +export default function QuestionSet({questionSet, updateAnswer = null, next = null}) { const questionSetClass = [style.questionSet].join(" "); + const [selectedOptions, setSelectedOptions] = useState([]); + const setFinished = questionSet.questions.length === selectedOptions.length; + const selectOption = (questionId, optionId) => { + if(!selectedOptions.includes(questionId)) setSelectedOptions([...selectedOptions, questionId]); + updateAnswer(questionId, optionId); + }; + + useEffect(() => { + if(!setFinished) return; + next(); + }, [setFinished]); return (
@@ -14,7 +26,7 @@ export default function QuestionSet({questionSet, updateSlide = null}) {

{question.text}

updateSlide(question.id, optionId)} + updateAnswer={(optionId) => selectOption(question.id, optionId)} />
))} @@ -31,5 +43,6 @@ QuestionSet.propTypes = { })).isRequired, setImage: PropTypes.string.isRequired }).isRequired, - updateSlide: PropTypes.func + updateAnswer: PropTypes.func, + next: PropTypes.func }; diff --git a/src/components/survey/generic/responses.js b/src/components/survey/generic/responses.js index 2807914d..20ddf469 100644 --- a/src/components/survey/generic/responses.js +++ b/src/components/survey/generic/responses.js @@ -1,9 +1,15 @@ import PropTypes from "prop-types"; +import {useState} from "react"; import style from "./style.scss"; -export default function Responses({responseOptions = [], updateSlide = null}) { +export default function Responses({responseOptions = [], updateAnswer = null}) { const buttonClass = ["traitify--response-button", style.response].join(" "); const buttonWidth = (text) => (text.length > 20 ? "100%" : "auto"); + const [activeButton, setActiveButton] = useState(null); + const selectOption = (optionId) => { + setActiveButton(optionId); + updateAnswer(optionId); + }; return (
@@ -11,8 +17,8 @@ export default function Responses({responseOptions = [], updateSlide = null}) { + + ); + } + return ( {currentQuestionSet @@ -50,9 +65,9 @@ export default function Generic() { )} {questionSetIndex > 0 && ( - )} diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 9bb43680..98a185c7 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -9,6 +9,7 @@ position: relative; z-index: 1; padding: $buffer-lg; + text-align: center; @include min-width("xs") { aspect-ratio: 2/1; @@ -20,27 +21,21 @@ border-radius: $border-radius-sm * 0.5; margin: $buffer $buffer * 0.5; height: 48px; + vertical-align: middle; - .back { - border-radius: $border-radius-sm; - bottom: 30px; - opacity: 0.7; - padding: $buffer * 0.5; - position: absolute; - width: 40px; - z-index: 1; - - @include theme("color", "text-light"); - @include theme("background-color", "fullscreen"); - &:hover{ opacity: 1; } - svg { - display: block; - height: 100%; - margin: auto; - width: auto; + &.back { + padding: $buffer * 0.5 $buffer-lg * 0.5; + width: 150px; + + .icon { + margin-right: $buffer-sm; + font-size: $font-size-lg; + } } } - .back { left: 30px; } + + .markdown { + text-align: center; } .progress { @@ -108,8 +103,8 @@ outline-width: 1px; } &.btnActive { - background-color: #25C9D0; - color: #FFFFFF; + background-color: $-blue; + color: $-white; border-color: transparent; } &.btnDisabled { @@ -117,4 +112,15 @@ } } +button.btnPrimary { + background-color: $-blue; + color: $-white; + display: inline-block; + padding: $buffer-lg * .5 $buffer; + text-align: center; + border: transparent; + max-width: 110px; + margin: $buffer-lg auto; +} + .hide { display: none; } \ No newline at end of file From aeeb84b5f7e43c4950bad42913ea37198c149709 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Tue, 17 Jun 2025 19:17:08 +0700 Subject: [PATCH 08/16] add show instructions --- src/components/survey/generic/index.js | 23 +++++++++++++++ src/components/survey/generic/style.scss | 36 +++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 306a5493..51ed4212 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -2,6 +2,7 @@ import {faArrowLeft} from "@fortawesome/free-solid-svg-icons"; import {useEffect, useState} from "react"; import Icon from "components/common/icon"; import Markdown from "components/common/markdown"; +import Modal from "components/common/modal"; import useAssessment from "lib/hooks/use-assessment"; import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; @@ -11,6 +12,7 @@ import style from "./style.scss"; export default function Generic() { const [questionSetIndex, setQuestionSetIndex] = useState(0); const [answers, setAnswers] = useState([]); + const [showInstructions, setShowInstructions] = useState(false); const [showConclusions, setShowConclusions] = useState(false); const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; @@ -36,6 +38,10 @@ export default function Generic() { setShowConclusions(true); }; + useEffect(() => { + setShowInstructions(true); + }, [assessment]); + useEffect(() => { if(!finished) { return; } onSubmit(); @@ -70,6 +76,23 @@ export default function Generic() { Go Back )} + {showInstructions + && ( + setShowInstructions(false)} + className={style.instructions} + > + + {assessment.instructions} + +
+
+ + +
+
+ )} ); } diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 98a185c7..4b202bdb 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -16,6 +16,11 @@ max-width: $breakpoint-md * 0.85; } + .modalContainer { + overflow: auto; + max-width: 600px; + } + button { border: 1px solid #DADCE0; border-radius: $border-radius-sm * 0.5; @@ -123,4 +128,33 @@ button.btnPrimary { margin: $buffer-lg auto; } -.hide { display: none; } \ No newline at end of file +.hide { display: none; } + +.grayDivider { + color: #555555; + width: 100%; + margin: $buffer * 3 auto $buffer * .5; + line-height: inherit; + clear: both; + user-select: none; + break-after: page; + border: $border-width solid #e8e8ec; + border-radius: $border-radius-sm; +} + +.footer { + display: flex; + justify-content: flex-end; + gap: $buffer * 0.5; + + .cancelBtn { + @include buttonLight("text-dark"); + @include theme("border-color", "border-light"); + border: $border-width solid; + border-radius: $border-radius-sm * 0.5; + } + + .btnPrimary { + margin: $buffer; + } +} \ No newline at end of file From 612d3fe92db130c3fb4088f2be5077531c2906c9 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Tue, 17 Jun 2025 23:13:01 +0700 Subject: [PATCH 09/16] update Modal common --- src/components/common/modal/index.js | 8 +++++--- src/components/survey/generic/index.js | 18 +++++++++++++++--- src/components/survey/generic/style.scss | 8 +++----- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/components/common/modal/index.js b/src/components/common/modal/index.js index b976a043..0ad8fbcf 100644 --- a/src/components/common/modal/index.js +++ b/src/components/common/modal/index.js @@ -4,11 +4,12 @@ import Icon from "components/common/icon"; import useTranslate from "lib/hooks/use-translate"; import style from "./style.scss"; -export default function Modal({children, onClose, title}) { +export default function Modal({children, onClose, title, containerClass = ""}) { const translate = useTranslate(); + const sectionClass = [style.modalContainer, containerClass].join(" "); return (
-
+
{title}
@@ -35,5 +36,6 @@ export default function Modal({children, onClose, title}) { Modal.propTypes = { children: PropTypes.node.isRequired, onClose: PropTypes.func.isRequired, - title: PropTypes.string.isRequired + title: PropTypes.string.isRequired, + containerClass: PropTypes.string }; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 51ed4212..bd0c3aeb 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -81,15 +81,27 @@ export default function Generic() { setShowInstructions(false)} - className={style.instructions} + containerClass={style.modalContainer} > {assessment.instructions}
- - + +
)} diff --git a/src/components/survey/generic/style.scss b/src/components/survey/generic/style.scss index 4b202bdb..c7c6b4a3 100644 --- a/src/components/survey/generic/style.scss +++ b/src/components/survey/generic/style.scss @@ -112,9 +112,6 @@ color: $-white; border-color: transparent; } - &.btnDisabled { - &:active, &:hover { @include theme("background-color", "inactive"); } - } } button.btnPrimary { @@ -145,16 +142,17 @@ button.btnPrimary { .footer { display: flex; justify-content: flex-end; - gap: $buffer * 0.5; + gap: $buffer * 0.25; .cancelBtn { @include buttonLight("text-dark"); @include theme("border-color", "border-light"); border: $border-width solid; border-radius: $border-radius-sm * 0.5; + margin: $buffer * 0.5; } .btnPrimary { - margin: $buffer; + margin: $buffer * 0.5; } } \ No newline at end of file From 0ca367b19b17de8ffc025783855588bf7453736b Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 11:19:23 +0700 Subject: [PATCH 10/16] submit answers --- src/components/survey/generic/index.js | 25 ++++++++++++++++++++++--- src/lib/graphql/generic.js | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index bd0c3aeb..4fbf11be 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -4,6 +4,8 @@ import Icon from "components/common/icon"; import Markdown from "components/common/markdown"; import Modal from "components/common/modal"; import useAssessment from "lib/hooks/use-assessment"; +import useGraphql from "lib/hooks/use-graphql"; +import useHttp from "lib/hooks/use-http"; import useTranslate from "lib/hooks/use-translate"; import Container from "./container"; import QuestionSet from "./question-set"; @@ -14,6 +16,7 @@ export default function Generic() { const [answers, setAnswers] = useState([]); const [showInstructions, setShowInstructions] = useState(false); const [showConclusions, setShowConclusions] = useState(false); + const assessment = useAssessment({surveyType: "generic"}); const questionSets = assessment ? assessment.questionSets : []; const questionCount = questionSets.reduce((count, set) => count + set.questions.length, 0); @@ -21,8 +24,10 @@ export default function Generic() { const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; const finished = questionSets.length > 0 && questionCount === answers.length; - const props = {progress}; + const graphQL = useGraphql(); + const http = useHttp(); const translate = useTranslate(); + const props = {progress}; const updateAnswer = (questionId, selectedOptionId) => { const currentAnswers = answers.filter((answer) => answer.questionId !== questionId); @@ -34,8 +39,22 @@ export default function Generic() { const back = () => { setQuestionSetIndex(questionSetIndex - 1); }; const onSubmit = () => { - console.log("Submitting answers:", answers); - setShowConclusions(true); + const query = graphQL.generic.update; + const variables = { + surveyID: localStorage.getItem("surveyID"), + profileID: localStorage.getItem("profileID"), + answers + }; + + http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { + if(!errors && data.submitGenericAssessmentAnswers.success) { + setShowConclusions(true); + } else { + console.warn(errors || data); // eslint-disable-line no-console + + // setTimeout(() => setSubmitAttempts((x) => x + 1), 2000); + } + }); }; useEffect(() => { diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index f60bc483..fd9c2ba3 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -43,4 +43,16 @@ export const questions = ` } `; +export const update = ` + mutation($profileID: ID!, $surveyID: ID!, $answers: [Answers]!) { + submitGenericAssessmentAnswers(profileId: $profileID, surveyId: $surveyID, answers: $answers) { + id + surveyId + profileId + startedAt + completedAt + } + } +`; + export const path = "/generic-assessments/graphql"; From aa365bb19772d77a3cb084ef7e6b273f8c469d72 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 17:20:41 +0700 Subject: [PATCH 11/16] use assessmentID --- src/components/container/hooks/use-base-effect.js | 5 +---- src/components/container/hooks/use-props.js | 6 ++---- src/components/status/assessment.js | 15 --------------- src/components/survey/generic/index.js | 5 ++--- src/lib/graphql/generic.js | 15 +++++++++++---- src/lib/recoil/assessment.js | 14 ++++++-------- src/lib/recoil/base.js | 1 - 7 files changed, 22 insertions(+), 39 deletions(-) diff --git a/src/components/container/hooks/use-base-effect.js b/src/components/container/hooks/use-base-effect.js index 364cf648..8c0b69f5 100644 --- a/src/components/container/hooks/use-base-effect.js +++ b/src/components/container/hooks/use-base-effect.js @@ -7,8 +7,7 @@ import { orderIDState, orderState, packageIDState, - profileIDState, - surveyIDState + profileIDState } from "lib/recoil"; export default function useBaseEffect() { @@ -18,12 +17,10 @@ export default function useBaseEffect() { const setOrderID = useSetRecoilState(orderIDState); const setPackageID = useSetRecoilState(packageIDState); const setProfileID = useSetRecoilState(profileIDState); - const setSurveyID = useSetRecoilState(surveyIDState); useEffect(() => { setBenchmarkID(base.benchmarkID); }, [base.benchmarkID]); useEffect(() => { setOrderID(base.orderID); }, [base.orderID]); useEffect(() => { setPackageID(base.packageID); }, [base.packageID]); useEffect(() => { setProfileID(base.profileID); }, [base.profileID]); - useEffect(() => { setSurveyID(base.surveyID); }, [base.surveyID]); useDidUpdate(() => { resetOrder(); }, [base]); } diff --git a/src/components/container/hooks/use-props.js b/src/components/container/hooks/use-props.js index 59b18580..c375fb37 100644 --- a/src/components/container/hooks/use-props.js +++ b/src/components/container/hooks/use-props.js @@ -33,8 +33,7 @@ export default function useProps(props) { options = {}, orderID, packageID, - profileID, - surveyID + profileID } = props; useEffect(() => { @@ -78,8 +77,7 @@ export default function useProps(props) { if(orderID) { base.orderID = orderID; } if(packageID) { base.packageID = packageID; } if(profileID) { base.profileID = profileID; } - if(surveyID) { base.surveyID = surveyID; } setBase(base); - }, [assessmentID, benchmarkID, orderID, packageID, profileID, surveyID]); + }, [assessmentID, benchmarkID, orderID, packageID, profileID]); } diff --git a/src/components/status/assessment.js b/src/components/status/assessment.js index 42c5ee58..a6a9d366 100644 --- a/src/components/status/assessment.js +++ b/src/components/status/assessment.js @@ -9,21 +9,6 @@ import useTranslate from "lib/hooks/use-translate"; import {activeState} from "lib/recoil"; import style from "./style.scss"; -// TODO: Extract text to translate -const translations = { - complete: "Complete", - loading: "Loading", // Already translated - status: { - start: "Start Assessment" - }, - survey: { - cognitive_assessment: "Cognitive Assessment", - external_assessment: "External Assessment", - generic_assessment: "Generic Assessment", - personality_assessment: "Personality Assessment" - } -}; - function Button({assessment}) { const listener = useListener(); const options = useOption("status") || {}; diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 4fbf11be..8e59d909 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -41,13 +41,12 @@ export default function Generic() { const onSubmit = () => { const query = graphQL.generic.update; const variables = { - surveyID: localStorage.getItem("surveyID"), - profileID: localStorage.getItem("profileID"), + assessmentID: assessment.assessment.id, answers }; http.post(graphQL.generic.path, {query, variables}).then(({data, errors}) => { - if(!errors && data.submitGenericAssessmentAnswers.success) { + if(!errors && data.submitGenericAssessmentAnswers) { setShowConclusions(true); } else { console.warn(errors || data); // eslint-disable-line no-console diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index fd9c2ba3..deee00c9 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -20,8 +20,8 @@ export const create = ` `; export const questions = ` - query($profileID: ID!, $surveyID: ID!) { - genericAssessmentQuestions(profileId: $profileID, surveyId: $surveyID) { + query($assessmentID: ID!) { + genericAssessmentQuestions(assessmentId: $assessmentID) { id name conclusions @@ -39,13 +39,20 @@ export const questions = ` } } } + assessment { + id + surveyId + profileId + startedAt + completedAt + } } } `; export const update = ` - mutation($profileID: ID!, $surveyID: ID!, $answers: [Answers]!) { - submitGenericAssessmentAnswers(profileId: $profileID, surveyId: $surveyID, answers: $answers) { + mutation($assessmentID: ID!, $answers: [Answers]!) { + submitGenericAssessmentAnswers(assessmentId: $assessmentID, answers: $answers) { id surveyId profileId diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 60bea727..8d6aa67c 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -6,9 +6,7 @@ import { graphqlState, httpState, localeState, - profileIDState, - safeCacheKeyState, - surveyIDState + safeCacheKeyState } from "./base"; // TODO: Return error instead of null for cognitive and external @@ -117,7 +115,7 @@ export const genericAssessmentQuery = selectorFamily({ const http = get(httpState); const params = { query: GraphQL.generic.questions, - variables: {profileID: get(profileIDState), surveyID: get(surveyIDState)} + variables: {assessmentID: id} }; const response = await http.post({path: GraphQL.generic.path, params}); @@ -126,12 +124,12 @@ export const genericAssessmentQuery = selectorFamily({ return null; } - const questions = response.data.genericAssessmentQuestions; - if(!questions?.length) { return questions; } + const survey = response.data.genericAssessmentQuestions; + if(!survey?.length || !survey?.assessment.completedAt) { return survey; } - cache.set(cacheKey, questions); + cache.set(cacheKey, survey); - return questions; + return survey; }, key: "assessment/generic" }); diff --git a/src/lib/recoil/base.js b/src/lib/recoil/base.js index e86075ba..108a4b89 100644 --- a/src/lib/recoil/base.js +++ b/src/lib/recoil/base.js @@ -24,7 +24,6 @@ export const optionsState = atom({default: null, key: "options"}); export const orderIDState = atom({default: null, key: "order-id"}); export const packageIDState = atom({default: null, key: "package-id"}); export const profileIDState = atom({default: null, key: "profile-id"}); -export const surveyIDState = atom({default: null, key: "survey-id"}); // NOTE: Breaking up state prevents over-triggering selectors export const activeIDState = selector({ From 2874102b1581d39803bbbf4b2461161214a16e94 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 17:21:07 +0700 Subject: [PATCH 12/16] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89997123..446aa2de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "traitify-widgets", - "version": "3.7.1", + "version": "3.8.0", "description": "Traitiy Widgets", "repository": { "type": "git", From 8a997c1ded9c4ea6953ec2460e795c93dea487eb Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 20 Jun 2025 17:38:44 +0700 Subject: [PATCH 13/16] refactor --- public/index.js | 5 ----- src/lib/traitify.js | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/public/index.js b/public/index.js index 11688202..e2907913 100644 --- a/public/index.js +++ b/public/index.js @@ -63,11 +63,6 @@ function createWidget() { if(!orderID) { return; } Traitify.listener.on("Survey.start", (x) => console.log(x)); Traitify.options.orderID = orderID; - } else if(surveyType === "generic") { - Traitify.options.surveyID = cache.get("surveyID"); - Traitify.options.profileID = cache.get("profileID"); - Traitify.options.assessmentID = cache.get("assessmentID"); - Traitify.options.surveyType = surveyType; } else { const assessmentID = cache.get("assessmentID"); console.log("createWidget", {assessmentID}); diff --git a/src/lib/traitify.js b/src/lib/traitify.js index 59004a19..2c5db73e 100644 --- a/src/lib/traitify.js +++ b/src/lib/traitify.js @@ -29,8 +29,7 @@ export default class Traitify { "locale", "orderID", "packageID", - "profileID", - "surveyID" + "profileID" ]); return {...objects, ...props, options}; From a17c8c47cc016cc4efdc9445f7cc55cb3d43c884 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Tue, 24 Jun 2025 17:48:06 +0700 Subject: [PATCH 14/16] update graphql responses --- public/index.js | 2 +- src/components/survey/generic/index.js | 10 +++---- src/lib/graphql/generic.js | 38 +++++++++++++------------- src/lib/recoil/assessment.js | 8 +++--- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/public/index.js b/public/index.js index e2907913..09ad1d74 100644 --- a/public/index.js +++ b/public/index.js @@ -458,7 +458,7 @@ function setupGeneric() { Traitify.http.post(Traitify.GraphQL.generic.path, {query, variables}).then((response) => { try { - const options = response.data.genericAssessments + const options = response.data.genericSurveys .map(({id, name}) => ({text: name, value: id})) .sort((a, b) => a.text.localeCompare(b.text)); diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 8e59d909..2af44b0a 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -18,7 +18,7 @@ export default function Generic() { const [showConclusions, setShowConclusions] = useState(false); const assessment = useAssessment({surveyType: "generic"}); - const questionSets = assessment ? assessment.questionSets : []; + const questionSets = assessment ? assessment.survey.questionSets : []; const questionCount = questionSets.reduce((count, set) => count + set.questions.length, 0); const currentQuestionSet = questionSets ? questionSets[questionSetIndex] : {}; const progress = questionSetIndex >= 0 ? (questionSetIndex / questionSets.length) * 100 : 0; @@ -41,7 +41,7 @@ export default function Generic() { const onSubmit = () => { const query = graphQL.generic.update; const variables = { - assessmentID: assessment.assessment.id, + assessmentID: assessment.id, answers }; @@ -69,7 +69,7 @@ export default function Generic() { return ( - {assessment.conclusions} + {assessment.survey.conclusions} @@ -102,7 +102,7 @@ export default function Generic() { containerClass={style.modalContainer} > - {assessment.instructions} + {assessment.survey.instructions}
@@ -118,7 +118,7 @@ export default function Generic() { className={style.btnPrimary} onClick={() => setShowInstructions(false)} > - {assessment.instructionButton} + {assessment.survey.instructionButton}
diff --git a/src/lib/graphql/generic.js b/src/lib/graphql/generic.js index deee00c9..672325ff 100644 --- a/src/lib/graphql/generic.js +++ b/src/lib/graphql/generic.js @@ -1,6 +1,6 @@ export const surveys = ` query($localeKey: String!) { - genericAssessments(localeKey: $localeKey) { + genericSurveys(localeKey: $localeKey) { id name } @@ -21,31 +21,31 @@ export const create = ` export const questions = ` query($assessmentID: ID!) { - genericAssessmentQuestions(assessmentId: $assessmentID) { + genericSurveyQuestions(assessmentId: $assessmentID) { id - name - conclusions - instructions - instructionButton - questionSets { - text - setImage - questions { - id + surveyId + profileId + startedAt + completedAt + survey { + id + name + conclusions + instructions + instructionButton + questionSets { text - responseOptions { + setImage + questions { id text + responseOptions { + id + text + } } } } - assessment { - id - surveyId - profileId - startedAt - completedAt - } } } `; diff --git a/src/lib/recoil/assessment.js b/src/lib/recoil/assessment.js index 8d6aa67c..0373e1b5 100644 --- a/src/lib/recoil/assessment.js +++ b/src/lib/recoil/assessment.js @@ -124,12 +124,12 @@ export const genericAssessmentQuery = selectorFamily({ return null; } - const survey = response.data.genericAssessmentQuestions; - if(!survey?.length || !survey?.assessment.completedAt) { return survey; } + const assessment = response.data.genericSurveyQuestions; + if(!assessment?.length || !assessment?.completedAt) { return assessment; } - cache.set(cacheKey, survey); + cache.set(cacheKey, assessment); - return survey; + return assessment; }, key: "assessment/generic" }); From 8e04610261036c836fbd6688e2818cb221ef10b6 Mon Sep 17 00:00:00 2001 From: Duy Huynh Date: Fri, 27 Jun 2025 15:30:28 +0700 Subject: [PATCH 15/16] refactor code --- public/index.js | 9 ++++----- src/components/common/modal/index.js | 8 ++++---- src/components/results/generic/index.js | 12 ++++++++++++ src/components/survey/generic/container.js | 5 ++--- src/components/survey/generic/index.js | 5 ++--- src/components/survey/generic/question-set.js | 6 +++--- src/components/survey/generic/responses.js | 4 ++-- src/components/survey/index.js | 2 +- src/lib/recoil/assessment.js | 2 +- 9 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 src/components/results/generic/index.js diff --git a/public/index.js b/public/index.js index 09ad1d74..4a9486c1 100644 --- a/public/index.js +++ b/public/index.js @@ -389,9 +389,9 @@ function setupDom() { options: [ {text: "Benchmark", value: "benchmark"}, {text: "Cognitive", value: "cognitive"}, + {text: "Generic", value: "generic"}, {text: "Order", value: "order"}, - {text: "Personality", value: "personality"}, - {text: "Generic", value: "generic"} + {text: "Personality", value: "personality"} ], text: "Survey Type:" })); @@ -479,8 +479,7 @@ function setupTraitify() { const environment = cache.get("environment"); if(environment === "staging") { - // Traitify.http.host = "https://api.stag.awse.traitify.com"; - Traitify.http.host = "http://localhost:4000/"; + Traitify.http.host = "https://api.stag.awse.traitify.com"; } else { Traitify.http.host = "https://api.traitify.com"; } @@ -516,6 +515,6 @@ function onSurveyTypeChange(e) { setupTraitify(); setupDom(); -// setupCognitive(); +setupCognitive(); setupGeneric(); createWidget(); diff --git a/src/components/common/modal/index.js b/src/components/common/modal/index.js index 0ad8fbcf..c6a6c652 100644 --- a/src/components/common/modal/index.js +++ b/src/components/common/modal/index.js @@ -4,9 +4,9 @@ import Icon from "components/common/icon"; import useTranslate from "lib/hooks/use-translate"; import style from "./style.scss"; -export default function Modal({children, onClose, title, containerClass = ""}) { +export default function Modal({children, containerClass = null, onClose, title}) { const translate = useTranslate(); - const sectionClass = [style.modalContainer, containerClass].join(" "); + const sectionClass = [style.modalContainer, containerClass].filter(Boolean).join(" "); return (
@@ -35,7 +35,7 @@ export default function Modal({children, onClose, title, containerClass = ""}) { Modal.propTypes = { children: PropTypes.node.isRequired, + containerClass: PropTypes.string, onClose: PropTypes.func.isRequired, - title: PropTypes.string.isRequired, - containerClass: PropTypes.string + title: PropTypes.string.isRequired }; diff --git a/src/components/results/generic/index.js b/src/components/results/generic/index.js new file mode 100644 index 00000000..4bd8f120 --- /dev/null +++ b/src/components/results/generic/index.js @@ -0,0 +1,12 @@ +import useResults from "lib/hooks/use-results"; + +export default function Generic() { + const results = useResults({surveyType: "generic"}); + console.log("Generic assessment result:", results); + return ( +
+

Generic Report

+

This is a placeholder for a generic report. Please customize it as needed.

+
+ ); +} diff --git a/src/components/survey/generic/container.js b/src/components/survey/generic/container.js index 7642ccf6..491ad244 100644 --- a/src/components/survey/generic/container.js +++ b/src/components/survey/generic/container.js @@ -4,12 +4,11 @@ import style from "./style.scss"; export default function Container({children, progress}) { return (
- {progress < 100 - && ( + {progress < 100 && (
- )} + )} {children}
); diff --git a/src/components/survey/generic/index.js b/src/components/survey/generic/index.js index 2af44b0a..e4453c5c 100644 --- a/src/components/survey/generic/index.js +++ b/src/components/survey/generic/index.js @@ -78,15 +78,14 @@ export default function Generic() { return ( - {currentQuestionSet - && ( + {currentQuestionSet && ( - )} + )} {questionSetIndex > 0 && (