From 03539d03d23b27a772f210b6c89f6851964894eb Mon Sep 17 00:00:00 2001 From: Ryczko Date: Sat, 11 May 2024 14:34:27 +0200 Subject: [PATCH] Feature/Logic path --- cypress/.eslintrc.json | 1 + ...{thankyouPage.cy.ts => answerSurvey.cy.ts} | 17 +- .../e2e/{login.cy.ts => authorization.cy.ts} | 52 ++-- cypress/e2e/createSurvey.cy.ts | 15 +- lib/axiosConfig.ts | 17 +- locales/en/surveyCreate.json | 7 +- prisma/schema.prisma | 47 ++-- .../components/AuthFormWrapper.tsx | 2 +- .../AddQuestionButton/AddQuestionButton.tsx | 4 +- .../components/LogicalJump/Condition.tsx | 122 +++++++++ .../components/LogicalJump/LogicalJump.tsx | 72 +++++ .../NewQuestionModal/NewQuestionModal.tsx | 27 +- .../components/PreviewPanel/PreviewPanel.tsx | 25 +- .../ChoiceAdvancedSettings.tsx | 39 +++ .../EmojiAdvancedSettings.tsx | 39 +++ .../AdvancedSettings/RateAdvancedSettings.tsx | 46 ++++ .../utils/getAvailableComparisons.ts | 14 + .../utils/getAvailableJumps.ts | 25 ++ .../utils/getAvailableOptions.ts | 8 + .../EmojiQuestionBlock/EmojiQuestionBlock.tsx | 2 +- .../QuestionBlocks/QuestionBlockFactory.tsx | 39 ++- .../QuestionBlockWrapper.tsx | 52 +++- .../QuestionsSection/QuestionsSection.tsx | 15 +- .../createSurveyManager.test.ts | 6 +- .../createSurveyManager.ts | 245 ++++++++++++++---- .../features/SurveyDisplay/SurveyDisplay.tsx | 8 +- .../SurveyDisplay/SurveyDisplayContent.tsx | 20 +- .../AllQuestionsView/AllQuestionsView.tsx | 2 +- .../AnswersComponentFactory.tsx | 2 +- .../SurveyDisplay/components/ThankYou.tsx | 7 +- .../managers/surveyAnswerManager.ts | 85 +++++- .../components/SurveyRow/SurveyRow.tsx | 2 +- .../components/DataCard/DataCard.tsx | 2 +- .../ResultsComponents/ResultComponent.tsx | 2 +- src/pages/api/survey/[id].ts | 7 +- src/pages/api/survey/index.ts | 50 +++- src/pages/survey/[surveyId]/thank-you.tsx | 21 -- src/shared/components/Select/Select.tsx | 110 ++++++++ src/shared/components/Tabs/Tabs.tsx | 2 +- src/shared/constants/routesConfig.ts | 5 +- src/shared/constants/surveysConfig.ts | 13 +- src/types/QuestionWithLogicPath.ts | 5 + src/types/SurveyWithQuestions.ts | 5 +- 43 files changed, 1051 insertions(+), 235 deletions(-) rename cypress/e2e/{thankyouPage.cy.ts => answerSurvey.cy.ts} (73%) rename cypress/e2e/{login.cy.ts => authorization.cy.ts} (94%) create mode 100644 src/features/surveys/features/SurveyCreator/components/LogicalJump/Condition.tsx create mode 100644 src/features/surveys/features/SurveyCreator/components/LogicalJump/LogicalJump.tsx create mode 100644 src/features/surveys/features/SurveyCreator/components/QuestionBlocks/AdvancedSettings/ChoiceAdvancedSettings.tsx create mode 100644 src/features/surveys/features/SurveyCreator/components/QuestionBlocks/AdvancedSettings/EmojiAdvancedSettings.tsx create mode 100644 src/features/surveys/features/SurveyCreator/components/QuestionBlocks/AdvancedSettings/RateAdvancedSettings.tsx create mode 100644 src/features/surveys/features/SurveyCreator/components/QuestionBlocks/AdvancedSettings/utils/getAvailableComparisons.ts create mode 100644 src/features/surveys/features/SurveyCreator/components/QuestionBlocks/AdvancedSettings/utils/getAvailableJumps.ts create mode 100644 src/features/surveys/features/SurveyCreator/components/QuestionBlocks/AdvancedSettings/utils/getAvailableOptions.ts delete mode 100644 src/pages/survey/[surveyId]/thank-you.tsx create mode 100644 src/shared/components/Select/Select.tsx create mode 100644 src/types/QuestionWithLogicPath.ts diff --git a/cypress/.eslintrc.json b/cypress/.eslintrc.json index 721ba6c1..93d3757a 100644 --- a/cypress/.eslintrc.json +++ b/cypress/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:cypress/recommended"], "plugins": ["cypress"], "rules": { + "cypress/unsafe-to-chain-command": "off", "jest/expect-expect": "off", "no-relative-import-paths/no-relative-import-paths": "off", "@typescript-eslint/no-namespace": "off", diff --git a/cypress/e2e/thankyouPage.cy.ts b/cypress/e2e/answerSurvey.cy.ts similarity index 73% rename from cypress/e2e/thankyouPage.cy.ts rename to cypress/e2e/answerSurvey.cy.ts index a01e4b4a..af3f3246 100644 --- a/cypress/e2e/thankyouPage.cy.ts +++ b/cypress/e2e/answerSurvey.cy.ts @@ -1,7 +1,7 @@ import { faker } from '@faker-js/faker'; -describe('Create and Answer Survey', () => { - it('should create a survey and answer it and show thank you page', () => { +describe('Answering survey tests', () => { + it('should create a survey, answer it and show thank you page', () => { // Login before proceeding cy.login(); @@ -11,10 +11,15 @@ describe('Create and Answer Survey', () => { cy.get('[data-test-id="create-survey"]').click(); cy.url().should('include', '/survey/create'); - cy.get('input[name="survey-title"]').type(surveyTitle); + cy.get('input[name="survey-title"]').clear().type(surveyTitle); - cy.get('input[data-test-id="question-input-0"]').type(questionContent); - cy.get('input[data-test-id="question-input-1"]').type(questionContent); + cy.get('input[data-test-id="question-input-0"]') + .clear() + .type(questionContent); + + cy.get('input[data-test-id="question-input-1"]') + .clear() + .type(questionContent); cy.get('button[data-test-id="options-button"]').click(); cy.get('[data-test-id="one-per-step-toggle"]').click(); @@ -42,7 +47,7 @@ describe('Create and Answer Survey', () => { cy.contains('button', 'Send').should('be.visible').click(); // Verify Thank You Page - cy.url().should('include', '/thank-you'); + cy.get('[data-test-id="thank-you-header"]').should('exist'); }); }); }); diff --git a/cypress/e2e/login.cy.ts b/cypress/e2e/authorization.cy.ts similarity index 94% rename from cypress/e2e/login.cy.ts rename to cypress/e2e/authorization.cy.ts index e540a4de..1344c101 100644 --- a/cypress/e2e/login.cy.ts +++ b/cypress/e2e/authorization.cy.ts @@ -1,26 +1,26 @@ -import { faker } from '@faker-js/faker'; - -describe('Login Page', () => { - it('should sign up via email and password and redirect to home page', () => { - cy.visit('/login'); - cy.get('[data-test-id="signup-link"]').click(); - cy.url().should('include', '/signup'); - cy.title().should('include', 'Sign up'); - cy.reload(); - cy.title().should('include', 'Sign up'); - cy.get('input[name="name"]').type(faker.name.fullName()); - cy.get('input[type="email"]').type(faker.internet.email()); - cy.get('input[type="password"]').type(faker.internet.password()); - cy.get('form').submit(); - cy.url().should('include', '/'); - }); - - it('should not redirect to home page when login fails', () => { - cy.visit('/login'); - cy.title().should('include', 'Login'); - cy.get('input[type="email"]').type(faker.internet.email()); - cy.get('input[type="password"]').type(faker.internet.password()); - cy.get('form').submit(); - cy.url().should('include', '/login'); - }); -}); +import { faker } from '@faker-js/faker'; + +describe('Authorization tests', () => { + it('should not redirect to home page when login fails', () => { + cy.visit('/login'); + cy.title().should('include', 'Login'); + cy.get('input[type="email"]').type(faker.internet.email()); + cy.get('input[type="password"]').type(faker.internet.password()); + cy.get('form').submit(); + cy.url().should('include', '/login'); + }); + + it('should sign up via email and password and redirect to home page', () => { + cy.visit('/login'); + cy.get('[data-test-id="signup-link"]').click(); + cy.url().should('include', '/signup'); + cy.title().should('include', 'Sign up'); + cy.reload(); + cy.title().should('include', 'Sign up'); + cy.get('input[name="name"]').type(faker.name.fullName()); + cy.get('input[type="email"]').type(faker.internet.email()); + cy.get('input[type="password"]').type(faker.internet.password()); + cy.get('form').submit(); + cy.url().should('include', '/'); + }); +}); diff --git a/cypress/e2e/createSurvey.cy.ts b/cypress/e2e/createSurvey.cy.ts index 43b99823..bd6fb0c9 100644 --- a/cypress/e2e/createSurvey.cy.ts +++ b/cypress/e2e/createSurvey.cy.ts @@ -1,6 +1,6 @@ import { faker } from '@faker-js/faker'; -describe('Create Survey Page', () => { +describe('Creating survey tests', () => { it('should create a survey', () => { const surveyTitle = faker.lorem.sentence(); const questionContent = faker.lorem.sentence(); @@ -8,14 +8,19 @@ describe('Create Survey Page', () => { cy.login(); cy.get('[data-test-id="create-survey"]').click(); cy.url().should('include', '/survey/create'); - cy.get('input[name="survey-title"]').type(surveyTitle); - cy.get('input[data-test-id="question-input-0"]').type(questionContent); - cy.get('input[data-test-id="question-input-1"]').type(questionContent); + cy.get('input[name="survey-title"]').clear().type(surveyTitle); + + cy.get('input[data-test-id="question-input-0"]') + .clear() + .type(questionContent); + + cy.get('input[data-test-id="question-input-1"]') + .clear() + .type(questionContent); cy.get('button[name="create-survey"]').click(); - console.log('url', cy.url()); cy.url().should('include', '/survey/answer/'); cy.visit('/surveys'); cy.contains(surveyTitle); diff --git a/lib/axiosConfig.ts b/lib/axiosConfig.ts index 215eb589..72e98213 100644 --- a/lib/axiosConfig.ts +++ b/lib/axiosConfig.ts @@ -1,35 +1,36 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import axios from 'axios'; const instance = axios.create({ baseURL: process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000', }); -export const getFetch = (url: string, params = {}) => { - return instance({ +export const getFetch = (url: string, params = {}) => { + return instance({ method: 'GET', url, params, }).then((response) => response.data); }; -export const postFetch = (url: string, data = {}) => { - return instance({ +export const postFetch = (url: string, data: Req) => { + return instance({ method: 'POST', url, data, }).then((response) => response.data); }; -export const patchFetch = (url: string, data = {}) => { - return instance({ +export const patchFetch = (url: string, data: Req) => { + return instance({ method: 'PATCH', url, data, }).then((response) => response.data); }; -export const putFetch = (url: string, data = {}) => { - return instance({ +export const putFetch = (url: string, data: Req) => { + return instance({ method: 'PUT', url, data, diff --git a/locales/en/surveyCreate.json b/locales/en/surveyCreate.json index 63ed7223..2990bd85 100644 --- a/locales/en/surveyCreate.json +++ b/locales/en/surveyCreate.json @@ -4,7 +4,7 @@ "heading": "Create new survey", "editHeading": "Edit survey", "buttonCreate": "Create Survey", - "editNote": "Some action like adding and removing questions or changing answers are not available in edit mode.", + "editNote": "Some action like adding, removing questions or changing answers are not currently available in edit mode.", "editNoteTitle": "Note", "buttonSave": "Save changes", "discardChanges": "Discard changes", @@ -19,5 +19,8 @@ "fillRequiredFields": "Please fill all required fields", "surveyCreationSucessCopiedCorrectly": "and link copied to clipboard", "signInToCreateSurvey": "Sign in to create survey", - "options": "Display options" + "options": "Display options", + "GREATER_THAN": "Greater than", + "LESS_THAN": "Less than", + "EQUAL": "Equal" } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 715d6f7e..d99451c6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -63,14 +63,14 @@ model VerificationToken { } model Survey { - id String @id @default(auto()) @map("_id") @db.ObjectId - userId String @db.ObjectId - createdAt DateTime @default(now()) - title String - isActive Boolean - description String? - questions Question[] - answers Answer[] + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String @db.ObjectId + createdAt DateTime @default(now()) + title String + isActive Boolean + description String? + questions Question[] + answers Answer[] oneQuestionPerStep Boolean displayTitle Boolean hideProgressBar Boolean? @@ -89,16 +89,17 @@ model Question { isRequired Boolean options String[] order Int + logicPaths LogicPath[] survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade) } model Answer { - id String @id @default(auto()) @map("_id") @db.ObjectId - userId String? @db.ObjectId - createdAt DateTime @default(now()) - surveyId String @db.ObjectId + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String? @db.ObjectId + createdAt DateTime @default(now()) + surveyId String @db.ObjectId answerData AnswerData[] @@ -107,13 +108,19 @@ model Answer { } model AnswerData { - id String @id @default(auto()) @map("_id") @db.ObjectId - answerId String @db.ObjectId - questionId String @db.ObjectId + id String @id @default(auto()) @map("_id") @db.ObjectId + answerId String @db.ObjectId + questionId String @db.ObjectId providedAnswer String? - answer Answer @relation(fields: [answerId], references: [id], onDelete: Cascade) - } + answer Answer @relation(fields: [answerId], references: [id], onDelete: Cascade) +} + +type LogicPath { + nextQuestionId String? @db.ObjectId + comparisonType ComparisonType + selectedOption String +} enum QuestionType { EMOJI @@ -121,3 +128,9 @@ enum QuestionType { CHOICE RATE } + +enum ComparisonType { + EQUAL + GREATER_THAN + LESS_THAN +} diff --git a/src/features/authorization/components/AuthFormWrapper.tsx b/src/features/authorization/components/AuthFormWrapper.tsx index 1db3555f..1cef709f 100644 --- a/src/features/authorization/components/AuthFormWrapper.tsx +++ b/src/features/authorization/components/AuthFormWrapper.tsx @@ -3,7 +3,7 @@ import React, { PropsWithChildren } from 'react'; export default function AuthFormWrapper({ children }: PropsWithChildren) { return (
-
+
{children}
diff --git a/src/features/surveys/features/SurveyCreator/components/AddQuestionButton/AddQuestionButton.tsx b/src/features/surveys/features/SurveyCreator/components/AddQuestionButton/AddQuestionButton.tsx index 3a4a3ada..1e030814 100644 --- a/src/features/surveys/features/SurveyCreator/components/AddQuestionButton/AddQuestionButton.tsx +++ b/src/features/surveys/features/SurveyCreator/components/AddQuestionButton/AddQuestionButton.tsx @@ -5,10 +5,10 @@ import Button, { } from 'shared/components/Button/Button'; import NewQuestionModal from 'features/surveys/features/SurveyCreator/components/NewQuestionModal/NewQuestionModal'; import useModal from 'features/surveys/hooks/useModal'; -import { Question } from 'features/surveys/features/SurveyCreator/managers/createSurveyManager/createSurveyManager'; +import { DraftQuestion } from 'features/surveys/features/SurveyCreator/managers/createSurveyManager/createSurveyManager'; interface AddQuestionButtonProps { - onClick: (newQuestion: Question) => void; + onClick: (newQuestion: DraftQuestion) => void; } export const AddQuestionButton = ({ onClick }: AddQuestionButtonProps) => { diff --git a/src/features/surveys/features/SurveyCreator/components/LogicalJump/Condition.tsx b/src/features/surveys/features/SurveyCreator/components/LogicalJump/Condition.tsx new file mode 100644 index 00000000..394f2925 --- /dev/null +++ b/src/features/surveys/features/SurveyCreator/components/LogicalJump/Condition.tsx @@ -0,0 +1,122 @@ +import { + ArrowDownIcon, + ArrowRightIcon, + TrashIcon, +} from '@heroicons/react/outline'; +import React, { useEffect } from 'react'; +import Select from 'shared/components/Select/Select'; +import { useSurveyCreatorContext } from 'features/surveys/features/SurveyCreator/managers/createSurveyManager/context'; +import { ConditionOptions } from 'features/surveys/features/SurveyCreator/components/LogicalJump/LogicalJump'; +import { ComparisonType, QuestionType } from '@prisma/client'; + +interface ConditionProps { + elseCondition?: boolean; + stepIndex: number; + questionIndex: number; + conditionOptions?: ConditionOptions; +} + +export default function Condition({ + elseCondition, + questionIndex, + stepIndex, + conditionOptions, +}: ConditionProps) { + const { + removeLogicPath, + updateLogicPath, + questions, + isEditMode, + isSubmitted, + } = useSurveyCreatorContext(); + + const currentQuestion = questions[questionIndex]; + + useEffect(() => { + if (conditionOptions?.comparisons.length === 1) { + updateLogicPath(questionIndex, stepIndex, { + comparisonType: conditionOptions.comparisons[0].value as ComparisonType, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> +
+
+ {elseCondition ? ( +
+ + Else continue to the next question +
+ ) : ( + <> + + if this answer + {conditionOptions?.comparisons.length === 1 ? ( + ' is' + ) : ( + + updateLogicPath(questionIndex, stepIndex, { + selectedOption: option.value, + }) + } + options={conditionOptions?.options ?? []} + /> + jump to +